shared_ptr a una matriz: ¿debería usarse?

172

Solo una pequeña consulta al respecto shared_ptr.

¿Es una buena práctica usar shared_ptrseñalar a una matriz? Por ejemplo,

shared_ptr<int> sp(new int[10]);

Si no, ¿por qué no? Una razón por la que ya estoy al tanto es que uno no puede aumentar / disminuir el shared_ptr. Por lo tanto, no se puede usar como un puntero normal a una matriz.

tshah06
fuente
2
FWIT, también puedes considerar usarlo std::vector. Tendría que tener cuidado de pasar la matriz usando referencias para no hacer copias de ella. La sintaxis para acceder a los datos es más limpia que shared_ptr, y cambiar su tamaño es muy fácil. Y obtienes toda la bondad STL si alguna vez la quieres.
Nicu Stiurca
66
Si el tamaño de la matriz se determina en el momento de la compilación, también puede considerar usarlo std::array. Es casi lo mismo que una matriz en bruto, pero con una semántica adecuada para usar en la mayoría de los componentes de la biblioteca. Especialmente los objetos de ese tipo se destruyen con delete, no delete[]. Y a diferencia vector, almacena los datos directamente en el objeto, por lo que no obtiene una asignación adicional.
celtschk

Respuestas:

268

Con C ++ 17 , shared_ptrse puede usar para administrar una matriz asignada dinámicamente. El shared_ptrargumento de plantilla en este caso debe ser T[N]o T[]. Entonces puedes escribir

shared_ptr<int[]> sp(new int[10]);

Desde n4659, [util.smartptr.shared.const]

  template<class Y> explicit shared_ptr(Y* p);

Requiere: Y debe ser un tipo completo. La expresión delete[] p, cuando Tes un tipo de matriz o delete p, cuando Tno es un tipo de matriz, tendrá un comportamiento bien definido y no arrojará excepciones.
...
Observaciones: Cuando Tes un tipo de matriz, este constructor no podrá participar en la resolución de sobrecarga a menos que la expresión delete[] pestá bien formado y, o bien Tes U[N]y Y(*)[N]es convertible a T*, o Tes U[], y Y(*)[]se puede convertir en T*. ...

Para admitir esto, el tipo de miembro element_typeahora se define como

using element_type = remove_extent_t<T>;

Se puede acceder a los elementos de la matriz utilizando operator[]

  element_type& operator[](ptrdiff_t i) const;

Se requiere: get() != 0 && i >= 0 . Si Tes U[N], i < N. ...
Observaciones: cuando Tno es un tipo de matriz, no se especifica si se declara esta función miembro. Si se declara, no se especifica cuál es su tipo de retorno, excepto que la declaración (aunque no necesariamente la definición) de la función estará bien formada.


Antes de C ++ 17 , shared_ptrpodría no ser utilizado para administrar matrices dinámicamente asignados. Por defecto, shared_ptrllamará deleteal objeto administrado cuando no queden más referencias a él. Sin embargo, cuando asigna utilizando new[]debe llamar delete[], y no delete, para liberar el recurso.

Para usar correctamente shared_ptrcon una matriz, debe proporcionar un eliminador personalizado.

template< typename T >
struct array_deleter
{
  void operator ()( T const * p)
  { 
    delete[] p; 
  }
};

Cree el shared_ptr de la siguiente manera:

std::shared_ptr<int> sp(new int[10], array_deleter<int>());

Ahora shared_ptrllamará correctamente delete[]al destruir el objeto administrado.

El eliminador personalizado anterior puede ser reemplazado por

  • la std::default_deleteespecialización parcial para tipos de matriz

    std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>());
  • una expresión lambda

    std::shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });

Además, a menos que realmente necesite compartir la responsabilidad del objeto administrado, a unique_ptres más adecuado para esta tarea, ya que tiene una especialización parcial para los tipos de matriz.

std::unique_ptr<int[]> up(new int[10]); // this will correctly call delete[]

Cambios introducidos por las extensiones de C ++ para los fundamentos de la biblioteca

Otra especificación anterior a C ++ 17 a las enumeradas anteriormente fue proporcionada por la Especificación técnica de los fundamentos de la biblioteca , que aumentó shared_ptrpara permitirle trabajar de forma inmediata para los casos en que posee una matriz de objetos. El borrador actual de los shared_ptrcambios programados para este TS se puede encontrar en N4082 . Se podrá acceder a estos cambios a través del std::experimentalespacio de nombres y se incluirán en el <experimental/memory>encabezado. Algunos de los cambios relevantes para soportar shared_ptrmatrices son:

- La definición del tipo de miembro element_typecambia

typedef T element_type;

 typedef typename remove_extent<T>::type element_type;

- Miembro operator[]agregado

 element_type& operator[](ptrdiff_t i) const noexcept;

- A diferencia de la unique_ptrespecialización parcial para matrices, tanto shared_ptr<T[]>y shared_ptr<T[N]>será válida y ambos se traducirá en delete[]ser llamado en la matriz administrada de objetos.

 template<class Y> explicit shared_ptr(Y* p);

Requiere : Ydebe ser un tipo completo. La expresión delete[] p, cuando Tes un tipo de matriz o delete p, cuando Tno es un tipo de matriz, estará bien formada, tendrá un comportamiento bien definido y no arrojará excepciones. Cuando Tes U[N], Y(*)[N]será convertible a T*; cuando Tes U[], Y(*)[]será convertible a T*; de lo contrario, Y*será convertible a T*.

Pretoriano
fuente
9
+1, comentario: También hay Boost's shared-array.
jogojapan
55
@ tshah06 shared_ptr::getdevuelve un puntero al objeto gestionado. Para que pueda usarlo comosp.get()[0] = 1; ... sp.get()[9] = 10;
Pretoriano
55
ALT: std::shared_ptr<int> sp( new int[10], std::default_delete<int[]>() );ver también en.cppreference.com/w/cpp/memory/default_delete
yohjp
2
@Jeremy Si se conoce el tamaño en el momento de la compilación, no es necesario escribir una clase para eso, std::shared_ptr<std::array<int,N>>debería ser suficiente.
Pretoriano
13
¿Por qué unique_ptrobtiene esa especialización parcial pero shared_ptrno la obtiene ?
Adam
28

Una alternativa posiblemente más fácil que podría usar es shared_ptr<vector<int>>.

Timmmm
fuente
55
Sí lo es. O un vector es un superconjunto de una matriz: tiene la misma representación en memoria (más metadatos) pero es redimensionable. Realmente no hay situaciones en las que desee una matriz pero no pueda usar un vector.
Timmmm
2
La diferencia, aquí, es que el tamaño del vector ya no es estático, y el acceso a los datos se realizará con una doble indirección. Si el rendimiento no es el problema crítico, esto funciona, de lo contrario, compartir una matriz puede tener su propia razón.
Emilio Garavaglia
44
Entonces probablemente puedas usar shared_ptr<array<int, 6>>.
Timmmm
10
La otra diferencia es que es un poco más grande y más lenta que una matriz sin formato. Generalmente no es realmente un problema, pero no pretendamos que 1 == 1.1.
Andrew
2
Hay situaciones en las que el origen de los datos en la matriz significa que es difícil de manejar o innecesario convertir a un vector; como al obtener un marco de una cámara. (O, eso entiendo, de todos modos)
Narfanator