unique_ptr smart shared_ptr c++ c++11 smart-pointers

c++ - smart - ¿Hay algún uso para unique_ptr con matriz?



smart pointers c++ 11 (16)

std::unique_ptr tiene soporte para arrays, por ejemplo:

std::unique_ptr<int[]> p(new int[10]);

pero es necesario? probablemente es más conveniente usar std::vector o std::array .

¿Encuentras algún uso para esa construcción?


Al contrario de std::vector y std::array , std::unique_ptr puede poseer un puntero NULL.
Esto resulta útil cuando se trabaja con API de C que esperan una matriz o NULL:

void legacy_func(const int *array_or_null); void some_func() { std::unique_ptr<int[]> ptr; if (some_condition) { ptr.reset(new int[10]); } legacy_func(ptr.get()); }


Algunas personas no pueden darse el lujo de usar std::vector , incluso con asignadores. Algunas personas necesitan una matriz de tamaño dinámico, por lo que std::array está fuera. Y algunas personas obtienen sus matrices de otro código que se sabe que devuelve una matriz; y ese código no será reescrito para devolver un vector o algo así.

Al permitir unique_ptr<T[]> , usted satisface esas necesidades.

En resumen, usa unique_ptr<T[]> cuando lo necesite . Cuando las alternativas simplemente no van a funcionar para ti. Es una herramienta de último recurso.


En pocas palabras: es, con mucho, el más eficiente en memoria.

Una std::string viene con un puntero, una longitud y un búfer de "optimización de cadena corta". Pero mi situación es que necesito almacenar una cadena que casi siempre está vacía, en una estructura que tengo cientos de miles. En C, solo usaría char * , y sería nulo la mayor parte del tiempo. Lo cual también funciona para C ++, excepto que un char * no tiene destructor, y no sabe eliminarse a sí mismo. Por el contrario, un std::unique_ptr<char[]> se borrará a sí mismo cuando salga de su alcance. Un std::string vacío ocupa 32 bytes, pero un std::unique_ptr<char[]> ocupa 8 bytes, es decir, exactamente el tamaño de su puntero.

El mayor inconveniente es que, cada vez que quiero saber la longitud de la cadena, tengo que llamar a strlen .


Hay concesiones, y usted elige la solución que coincida con lo que desea. La parte superior de mi cabeza:

Tamaño inicial

  • vector y unique_ptr<T[]> permiten que el tamaño se especifique en tiempo de ejecución
  • array solo permite especificar el tamaño en el momento de la compilación

Redimensionamiento

  • array y unique_ptr<T[]> no permiten cambiar el tamaño
  • vector hace

Almacenamiento

  • vector y unique_ptr<T[]> almacenan los datos fuera del objeto (normalmente en el montón)
  • array almacena los datos directamente en el objeto

Proceso de copiar

  • array y vector permiten copiar
  • unique_ptr<T[]> no permite copiar

Intercambiar / mover

  • vector y unique_ptr<T[]> tienen O (1) swap tiempo y operaciones de movimiento
  • array tiene O (n) swap tiempo y operaciones de movimiento, donde n es el número de elementos en la matriz

Puntero / referencia / invalidación de iterador

  • array garantiza que los punteros, las referencias y los iteradores nunca se invalidarán mientras el objeto esté activo, incluso en swap()
  • unique_ptr<T[]> no tiene iteradores; los punteros y las referencias solo se invalidan mediante swap() mientras el objeto está activo. (Después de intercambiar, los punteros apuntan a la matriz con la que se intercambió, por lo que siguen siendo "válidos" en ese sentido).
  • vector puede invalidar los punteros, las referencias y los iteradores en cualquier reasignación (y proporciona algunas garantías de que la reasignación solo puede ocurrir en ciertas operaciones).

Compatibilidad con conceptos y algoritmos.

  • array y vector son ambos contenedores
  • unique_ptr<T[]> no es un contenedor

Tengo que admitir que esto parece ser una oportunidad para un poco de refactorización con un diseño basado en políticas.


He utilizado unique_ptr<char[]> para implementar un conjunto de memoria preasignado que se usa en un motor de juego. La idea es proporcionar grupos de memoria preasignados utilizados en lugar de asignaciones dinámicas para devolver resultados de solicitudes de colisión y otras cosas como la física de partículas sin tener que asignar / liberar memoria en cada marco. Es muy conveniente para este tipo de escenarios en los que necesita grupos de memoria para asignar objetos con una vida útil limitada (generalmente uno, 2 o 3 cuadros) que no requieren lógica de destrucción (solo desasignación de memoria).


Me enfrenté a un caso en el que tuve que usar std::unique_ptr<bool[]> , que estaba en la biblioteca HDF5 (una biblioteca para el almacenamiento eficiente de datos binarios, que se usa mucho en ciencia). Algunos compiladores (Visual Studio 2015 en mi caso) proporcionan compresión de std::vector<bool> (usando 8 bools en cada byte), que es una catástrofe para algo como HDF5, que no se preocupa por esa compresión. Con std::vector<bool> , HDF5 finalmente estaba leyendo basura debido a esa compresión.

¿Adivina quién estuvo allí para el rescate, en un caso donde std::vector no funcionó, y tuve que asignar una matriz dinámica limpiamente? :-)


Para responder a las personas que piensan que "tiene que" usar vector lugar de unique_ptr Tengo un caso en la programación de CUDA en la GPU cuando asigna memoria en el dispositivo, debe ir a una matriz de punteros (con cudaMalloc ). Luego, cuando recupere estos datos en el Host, debe buscar de nuevo un puntero y unique_ptr está bien para manejar el puntero fácilmente. El costo adicional de convertir double* a vector<double> es innecesario y conduce a una pérdida de rendimiento.


Pueden ser la respuesta más acertada posible cuando solo puede introducir un solo puntero a través de una API existente (mensaje de la ventana de pensamiento o parámetros de devolución de llamada relacionados con el subproceso) que tienen cierta medida de vida después de haber sido "atrapados" en el otro lado de la compuerta. pero que no está relacionado con el código de llamada:

unique_ptr<byte[]> data = get_some_data(); threadpool->post_work([](void* param) { do_a_thing(unique_ptr<byte[]>((byte*)param)); }, data.release());

Todos queremos que las cosas sean agradables para nosotros. C ++ es para las otras veces.


Scott Meyers tiene esto que decir en Effective Modern C ++

La existencia de std::unique_ptr para arreglos debería ser de interés intelectual únicamente para usted, porque std::array , std::vector , std::string son prácticamente mejores opciones de estructura de datos que los arreglos en bruto. Acerca de la única situación que puedo concebir cuando una std::unique_ptr<T[]> tendría sentido cuando usas una API de tipo C que devuelve un puntero en bruto a una matriz de montón de la que asumes la propiedad.

Sin embargo, creo que la respuesta de Charles Salvia es relevante: que std::unique_ptr<T[]> es la única forma de inicializar una matriz vacía cuyo tamaño no se conoce en el momento de la compilación. ¿Qué diría Scott Meyers sobre esta motivación para usar std::unique_ptr<T[]> ?


Se puede encontrar un patrón común en some llamadas a la API de Windows Win32 , en las que el uso de std::unique_ptr<T[]> puede ser útil, por ejemplo, cuando no sabe exactamente qué tan grande debe ser un búfer de salida al llamar a algunos Win32 API (que escribirá algunos datos dentro de ese búfer):

// Buffer dynamically allocated by the caller, and filled by some Win32 API function. // (Allocation will be made inside the ''while'' loop below.) std::unique_ptr<BYTE[]> buffer; // Buffer length, in bytes. // Initialize with some initial length that you expect to succeed at the first API call. UINT32 bufferLength = /* ... */; LONG returnCode = ERROR_INSUFFICIENT_BUFFER; while (returnCode == ERROR_INSUFFICIENT_BUFFER) { // Allocate buffer of specified length buffer.reset( BYTE[bufferLength] ); // // Or, in C++14, could use make_unique() instead, e.g. // // buffer = std::make_unique<BYTE[]>(bufferLength); // // // Call some Win32 API. // // If the size of the buffer (stored in ''bufferLength'') is not big enough, // the API will return ERROR_INSUFFICIENT_BUFFER, and the required size // in the [in, out] parameter ''bufferLength''. // In that case, there will be another try in the next loop iteration // (with the allocation of a bigger buffer). // // Else, we''ll exit the while loop body, and there will be either a failure // different from ERROR_INSUFFICIENT_BUFFER, or the call will be successful // and the required information will be available in the buffer. // returnCode = ::SomeApiCall(inParam1, inParam2, inParam3, &bufferLength, // size of output buffer buffer.get(), // output buffer pointer &outParam1, &outParam2); } if (Failed(returnCode)) { // Handle failure, or throw exception, etc. ... } // All right! // Do some processing with the returned information... ...


Si necesita una matriz dinámica de objetos que no son construibles por copia, entonces un puntero inteligente a una matriz es el camino a seguir. Por ejemplo, qué pasa si necesitas una matriz de atomics.


Un std::vector se puede copiar, mientras que unique_ptr<int[]> permite expresar la propiedad única de la matriz. std::array , por otro lado, requiere que el tamaño se determine en tiempo de compilación, lo que puede ser imposible en algunas situaciones.


Una de las razones por las que podría usar un unique_ptr es si no desea pagar el costo de tiempo de ejecución de value-initializing del value-initializing la matriz.

std::vector<char> vec(1000000); // allocates AND value-initializes 1000000 chars std::unique_ptr<char[]> p(new char[1000000]); // allocates storage for 1000000 chars

std::vector constructor y std::vector::resize() inicializarán el valor de T , pero new no lo hará si T es un POD.

Ver Objetos con valor inicializado en C ++ 11 y std :: vector constructor

Tenga en cuenta que vector::reserve no es una alternativa aquí: ¿Es seguro acceder al puntero en bruto después de std :: vector :: reserve?

Es la misma razón por la que un programador de C puede elegir malloc en lugar de calloc .


Una razón adicional para permitir y usar std::unique_ptr<T[]> , que no se ha mencionado en las respuestas hasta ahora: le permite declarar en forma ascendente el tipo de elemento de matriz.

Esto es útil cuando desea minimizar las declaraciones #include encadenadas en los encabezados (para optimizar el rendimiento de la compilación).

Por ejemplo -

myclass.h:

class ALargeAndComplicatedClassWithLotsOfDependencies; class MyClass { ... private: std::unique_ptr<ALargeAndComplicatedClassWithLotsOfDependencies[]> m_InternalArray; };

myclass.cpp:

#include "myclass.h" #include "ALargeAndComplicatedClassWithLotsOfDependencies.h" // MyClass implementation goes here

Con la estructura de código anterior, cualquiera puede #include "myclass.h" y usar MyClass , sin tener que incluir las dependencias de implementación internas requeridas por MyClass::m_InternalArray .

Si en m_InternalArray lugar se declara m_InternalArray como std::array<ALargeAndComplicatedClassWithLotsOfDependencies> , o std::vector<...> , respectivamente, se intentará utilizar un tipo incompleto, lo que es un error de compilación.


unique_ptr<char[]> puede usarse donde desee el rendimiento de C y la comodidad de C ++. Considera que necesitas operar con millones (vale, miles de millones si aún no confías) de cadenas. Almacenar cada uno de ellos en una string o vector<char> objeto sería un desastre para las rutinas de administración de memoria (montón). Especialmente si necesita asignar y eliminar cadenas diferentes muchas veces.

Sin embargo, puede asignar un único búfer para almacenar tantas cadenas. No le gustaría char* buffer = (char*)malloc(total_size); por razones obvias (si no es obvio, busque "por qué usar ptrs inteligentes"). Prefiere el unique_ptr<char[]> buffer(new char[total_size]);

Por analogía, las mismas consideraciones de rendimiento y conveniencia se aplican a los datos que no son de char (considere millones de vectores / matrices / objetos).


  • Necesita que su estructura contenga solo un puntero por razones de compatibilidad binaria.
  • Debe interactuar con una API que devuelve la memoria asignada con la new[]
  • Su empresa o proyecto tiene una regla general contra el uso de std::vector , por ejemplo, para evitar que programadores descuidados introduzcan copias accidentalmente
  • Desea evitar que programadores descuidados introduzcan copias accidentalmente en esta instancia.

Hay una regla general de que los contenedores de C ++ deben ser preferidos a rodar su propio con punteros. Es una regla general; tiene excepciones. Hay más; Estos son solo ejemplos.