c++ - smart - ¿Por qué permitir shared_ptr<T[N]>?
unique pointer (2)
Esta respuesta cita N4082 , que muestra que los próximos cambios en std::shared_ptr
permitirán las variantes T[]
y T[N]
:
A diferencia de la especialización parcial
unique_ptr
para matrices, tantoshared_ptr<T[]>
yshared_ptr<T[N]>
serán válidas y ambas harán que se llame adelete[]
en la matriz administrada de objetos.
template<class Y> explicit shared_ptr(Y* p);
Requiere :
Y
debe ser un tipo completo. La expresióndelete[] p
, cuandoT
es un tipo de matriz, odelete p
, cuandoT
no es un tipo de matriz, debe estar bien formada, debe tener un comportamiento bien definido y no debe arrojar excepciones. CuandoT
esU[N]
,Y(*)[N]
será convertible aT*
; cuandoT
esU[]
,Y(*)[]
será convertible aT*
; de lo contrario,Y*
será convertible aT*
.
A menos que me equivoque, una Y(*)[N]
solo podría formarse tomando la dirección de una matriz, que claramente no puede ser propiedad o eliminada por un shared_ptr
. Tampoco veo ninguna indicación de que N
se use de ninguna forma para imponer el tamaño del objeto gestionado.
¿Cuál es la motivación detrás de permitir la sintaxis T[N]
? ¿Reparte algún beneficio real y, de ser así, cómo se usa?
A menos que me equivoque, una
Y(*)[N]
solo podría formarse tomando la dirección de una matriz, que claramente no puede ser propiedad o eliminada por unshared_ptr
.
No olvide que shared_ptr
es una utilidad de gestión de recursos genérica y puede construirse con un desglose personalizado:
template<class Y, class D> shared_ptr(Y* p, D d);
Dicho desasignador proporcionado por el usuario puede realizar una acción que no sea delete
/ delete[]
. Por ejemplo, si la matriz en cuestión es una matriz de descriptores de archivos, el "deallocator" puede cerrarlos todos.
En tales casos, shared_ptr
no posee el objeto en el sentido ampliamente utilizado y, por lo tanto, puede vincularse a una matriz existente tomando su dirección.
Puede obtener un puntero a un objeto anidado que comparte la propiedad con std::shared_ptr
para el objeto que lo contiene. Si este objeto anidado resulta ser una matriz y desea acceder a él como un tipo de matriz, realmente necesita usar T[N]
con T
y N
adecuados:
#include <functional>
#include <iostream>
#include <iterator>
#include <memory>
#include <queue>
#include <utility>
#include <vector>
using queue = std::queue<std::function<void()>>;
template <typename T>
struct is_range {
template <typename R> static std::false_type test(R*, ...);
template <typename R> static std::true_type test(R* r, decltype(std::begin(*r))*);
static constexpr bool value = decltype(test(std::declval<T*>(), nullptr))();
};
template <typename T>
std::enable_if_t<!is_range<T>::value> process(T const& value) {
std::cout << "value=" << value << "/n";
}
template <typename T>
std::enable_if_t<is_range<T>::value> process(T const &range) {
std::cout << "range=[";
auto it(std::begin(range)), e(std::end(range));
if (it != e) {
std::cout << *it;
while (++it != e) {
std::cout << ", " << *it;
}
}
std::cout << "]/n";
}
template <typename P, typename T>
std::function<void()> make_fun(P const& p, T& value) {
return [ptr = std::shared_ptr<T>(p, &value)]{ process(*ptr); };
// here ----^
}
template <typename T, typename... M>
void enqueue(queue& q, std::shared_ptr<T> const& ptr, M... members) {
(void)std::initializer_list<bool>{
(q.push(make_fun(ptr, (*ptr).*members)), true)...
};
}
struct foo {
template <typename... T>
foo(int v, T... a): value(v), array{ a... } {}
int value;
int array[3];
std::vector<int> vector;
};
int main() {
queue q;
auto ptr = std::make_shared<foo>(1, 2, 3, 4);
enqueue(q, ptr, &foo::value, &foo::array, &foo::vector);
while (!q.empty()) {
q.front()();
q.pop();
}
}
En el código anterior q
solo hay una simple std::queue<std::function<void()>>
pero espero que puedas imaginar que podría ser un grupo de subprocesos que descargue el procesamiento a otro subproceso. El procesamiento realmente programado también es trivial, pero, de nuevo, espero que puedas imaginar que en realidad es una cantidad sustancial de trabajo.