std::unique_ptr
tiene soporte para matrices, por ejemplo:
std::unique_ptr<int[]> p(new int[10]);
pero es necesario? probablemente sea más conveniente usar std::vector
o std::array
.
¿Encuentra algún uso para esa construcción?
c++
c++11
smart-pointers
unique-ptr
pantano
fuente
fuente
std::shared_ptr<T[]>
, pero debería haberlo, y probablemente estará en C ++ 14 si alguien pudiera molestarse en escribir una propuesta. Mientras tanto, siempre hayboost::shared_array
.std::shared_ptr
<T []> está en c ++ 17 ahora.Respuestas:
Algunas personas no tienen el lujo de usar
std::vector
, incluso con asignadores. Algunas personas necesitan una matriz de tamaño dinámico, por lo questd::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 se reescribirá para devolver unvector
o algo.Al permitir
unique_ptr<T[]>
, usted atiende esas necesidades.En resumen, usas
unique_ptr<T[]>
cuando necesita . Cuando las alternativas simplemente no van a funcionar para usted. Es una herramienta de último recurso.fuente
vector
". Puede discutir si esos son requisitos razonables o no, pero no puede negar que existen .std::vector
si puede usarstd::unique_ptr
.unique_ptr
, pero ese tipo de proyectos realmente existen.Hay compensaciones, y usted elige la solución que coincide con lo que desea. La parte superior de mi cabeza:
Tamaño inicial
vector
yunique_ptr<T[]>
permitir que se especifique el tamaño en tiempo de ejecuciónarray
solo permite especificar el tamaño en tiempo de compilaciónRedimensionando
array
yunique_ptr<T[]>
no permitir cambiar el tamañovector
haceAlmacenamiento
vector
yunique_ptr<T[]>
almacenar los datos fuera del objeto (generalmente en el montón)array
almacena los datos directamente en el objetoProceso de copiar
array
yvector
permitir copiarunique_ptr<T[]>
no permite copiarIntercambiar / mover
vector
yunique_ptr<T[]>
tener O (1) tiemposwap
y operaciones de movimientoarray
tiene O (n) tiemposwap
y operaciones de movimiento, donde n es el número de elementos en la matrizInvalidación de puntero / referencia / iterador
array
garantiza que los punteros, las referencias y los iteradores nunca se invaliden mientras el objeto esté activo, incluso enswap()
unique_ptr<T[]>
no tiene iteradores; los punteros y las referencias solo se invalidanswap()
mientras el objeto está activo. (Después del intercambio, los punteros apuntan a la matriz con la que intercambió, por lo que siguen siendo "válidos" en ese sentido).vector
puede invalidar punteros, referencias e 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
yvector
son ambos contenedoresunique_ptr<T[]>
no es un contenedorDebo admitir que parece una oportunidad para refactorizar el diseño basado en políticas.
fuente
vector
. Luego, aumenta el tamaño o la capacidad devector
tal manera que obliga a una reasignación. Entonces ese iterador, puntero o referencia ya no apunta a ese elemento devector
. Esto es lo que queremos decir con "invalidación". Este problema no sucedearray
porque no hay "reasignación". En realidad, acabo de notar un detalle con eso, y lo he editado a la medida.unique_ptr<T[]>
porque no hay reasignación. Pero, por supuesto, cuando la matriz queda fuera de alcance, los punteros a elementos específicos seguirán siendo invalidados.T[]
, el tamaño (o información equivalente) debe estar en algún lugar paraoperator delete[]
destruir correctamente los elementos de la matriz. Sería bueno si el programador tuviera acceso a eso.Una razón por la que puede usar a
unique_ptr
es si no desea pagar el costo de tiempo de ejecución de la inicialización del valor de la matriz.El
std::vector
constructor y elstd::vector::resize()
valor inicializaránT
, peronew
no lo harán siT
es un POD.Ver objetos inicializados en C ++ 11 y std :: vector constructor
Tenga en cuenta que
vector::reserve
no es una alternativa aquí: ¿Es seguro acceder al puntero sin formato después de std :: vector :: reserve?Es la misma razón un programador C podría elegir
malloc
máscalloc
.fuente
std::vector
un asignador personalizado que evite la construcción de tipos que sonstd::is_trivially_default_constructible
y la destrucción de objetos que sonstd::is_trivially_destructible
, aunque estrictamente esto viola el estándar C ++ (ya que dichos tipos no se inicializan por defecto).std::unique_ptr
proporciona ninguna comprobación encuadernada, al contrario de muchasstd::vector
implementaciones.std::vector
el Estándar requiere que se registren los límites.at()
. Supongo que querías decir que algunas implementaciones tienen modos de depuración que también se registrarán.operator[]
, pero considero que eso es inútil para escribir un buen código portátil.Se
std::vector
puede copiar una, mientras queunique_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.fuente
unique_ptr
lugar deshared_ptr
. ¿Me estoy perdiendo de algo?unique_ptr
hace más que solo prevenir el mal uso accidental. También es más pequeño y más bajo queshared_ptr
. El punto es que, si bien es bueno tener semántica en una clase que evite el "mal uso", esa no es la única razón para usar un tipo en particular. Yvector
es mucho más útil como almacenamiento de matriz queunique_ptr<T[]>
, si no por otra razón que no sea el hecho de que tiene un tamaño .vector
aunique_ptr<T[]>
donde sea posible, en lugar de simplemente decir "no se puede copiar" y, por lo tanto, elegirunique_ptr<T[]>
cuando no desee copias. Evitar que alguien haga algo incorrecto no es necesariamente la razón más importante para elegir una clase.std::vector
tiene más gastos generales que unstd::unique_ptr
: utiliza ~ 3 punteros en lugar de ~ 1.std::unique_ptr
bloquea la construcción de la copia pero habilita la construcción del movimiento, que si semánticamente los datos con los que está trabajando solo se pueden mover pero no copiar, infecta elclass
contenido de los datos. Tener una operación sobre datos que no son válidos en realidad empeora su clase de contenedor, y "simplemente no lo use" no elimina todos los pecados. Tener que poner cada instancia de tustd::vector
en una clase donde deshabilitas manualmentemove
es un dolor de cabeza.std::unique_ptr<std::array>
tiene unsize
.Scott Meyers tiene esto que decir en Effective Modern C ++
Sin embargo, creo que la respuesta de Charles Salvia es relevante: esa
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 usarstd::unique_ptr<T[]>
?fuente
vector
stackoverflow.com/a/24852984/2436175 .Contrariamente a
std::vector
ystd::array
,std::unique_ptr
puede poseer un puntero NULL.Esto es útil cuando se trabaja con API de C que esperan una matriz o NULL:
fuente
Solía
unique_ptr<char[]>
implementar una agrupación de memoria preasignada utilizada en un motor de juego. La idea es proporcionar agrupaciones de memoria preasignadas utilizadas 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 cuadro. Es bastante conveniente para este tipo de escenarios en los que necesita grupos de memoria para asignar objetos con un tiempo de vida limitado (generalmente uno, 2 o 3 cuadros) que no requieren lógica de destrucción (solo desasignación de memoria).fuente
Se puede encontrar un patrón común en algunas llamadas de la API de Windows Win32 , en el 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 cuando se llama a alguna API de Win32 (que escribirá algunos datos dentro ese búfer):fuente
std::vector<char>
en estos casos.Me enfrenté a un caso en el que tenía que usar
std::unique_ptr<bool[]>
, que estaba en la biblioteca HDF5 (una biblioteca para el almacenamiento eficiente de datos binarios, que se usaba mucho en ciencia). Algunos compiladores (Visual Studio 2015 en mi caso) proporcionan compresiónstd::vector<bool>
(mediante el uso de 8 bools en cada byte), lo cual es una catástrofe para algo como HDF5, que no se preocupa por esa compresión. Constd::vector<bool>
, HDF5 finalmente estaba leyendo basura debido a esa compresión.¿Adivina quién estuvo allí para el rescate, en un caso en el
std::vector
que no funcionó, y necesitaba asignar una matriz dinámica limpiamente? :-)fuente
En pocas palabras: es, con mucho, el más eficiente en memoria.
A
std::string
viene con un puntero, una longitud y un búfer de "optimización de cadenas cortas". 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íachar *
, y sería nulo la mayor parte del tiempo. Lo que también funciona para C ++, excepto que achar *
no tiene destructor y no sabe eliminarse. Por el contrario, astd::unique_ptr<char[]>
se eliminará a sí mismo cuando salga del alcance. Un vacíostd::string
ocupa 32 bytes, pero un vacíostd::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 recurrir
strlen
a ella.fuente
Para responder a las personas que piensan que "tiene que" usarlas en
vector
lugar deunique_ptr
tener un caso en la programación CUDA en la GPU, cuando asigna memoria en el Dispositivo, debe elegir una matriz de puntero (concudaMalloc
). Luego, al recuperar estos datos en el Host, debe volver a buscar un puntero yunique_ptr
está bien manejarlo fácilmente. El costo adicional de la conversióndouble*
avector<double>
es innecesario y conduce a una pérdida de rendimiento.fuente
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 hacia adelante el tipo de elemento de matriz.Esto es útil cuando quieres minimizar el encadenado
#include
declaraciones en los encabezados (para optimizar el rendimiento de compilación)Por ejemplo -
Con la estructura de código anterior, cualquier persona puede
#include "myclass.h"
y el usoMyClass
, sin tener que incluir las dependencias de implementación internos requeridos porMyClass::m_InternalArray
.Si
m_InternalArray
se declarara como astd::array<ALargeAndComplicatedClassWithLotsOfDependencies>
, o astd::vector<...>
, respectivamente, el resultado sería un intento de uso de un tipo incompleto, que es un error en tiempo de compilación.fuente
class ALargeAndComplicatedClassWithLotsOfDependencies
. Entonces, lógicamente, no deberías toparte con tales escenarios.No puedo estar en desacuerdo con el espíritu de la respuesta aceptada con suficiente fuerza. ¿"Una herramienta de último recurso"? ¡Lejos de ahi!
A mi modo de ver, una de las características más fuertes de C ++ en comparación con C y con otros lenguajes similares es la capacidad de expresar restricciones para que puedan verificarse en el momento de la compilación y se pueda evitar el uso accidental. Entonces, al diseñar una estructura, pregúntese qué operaciones debería permitir. Todos los demás usos deberían estar prohibidos, y es mejor si tales restricciones pueden implementarse estáticamente (en el momento de la compilación) para que el mal uso provoque un error de compilación.
Entonces, cuando se necesita una matriz, las respuestas a las siguientes preguntas especifican su comportamiento: 1. ¿Es su tamaño a) dinámico en tiempo de ejecución, o b) estático, pero solo conocido en tiempo de ejecución, o c) estático y conocido en tiempo de compilación? 2. ¿Se puede asignar la matriz en la pila o no?
Y en base a las respuestas, esto es lo que veo como la mejor estructura de datos para dicha matriz:
Sí, creo
unique_ptr<std::array>
que también debería considerarse, y tampoco es una herramienta de último recurso. Solo piense qué se adapta mejor a su algoritmo.Todos estos son compatibles con API C simples a través del puntero sin formato a la matriz de datos (
vector.data()
/array.data()
/uniquePtr.get()
).PD Además de las consideraciones anteriores, también hay una de propiedad:
std::array
ystd::vector
tiene una semántica de valor (tiene soporte nativo para copiar y pasar por valor), mientrasunique_ptr<T[]>
que solo se puede mover (impone la propiedad única). Cualquiera puede ser útil en diferentes escenarios. Por el contrario, las matrices estáticas simples (int[N]
) y las matrices dinámicas simples (new int[10]
) no ofrecen ninguna y, por lo tanto, deben evitarse si es posible, lo que debería ser posible en la gran mayoría de los casos. Si eso no fuera suficiente, las matrices dinámicas simples tampoco ofrecen forma de consultar su tamaño, una oportunidad adicional para daños en la memoria y agujeros de seguridad.fuente
Pueden ser la respuesta más correcta posible cuando solo puede introducir un solo puntero a través de una API existente (mensaje de ventana de pensamiento o parámetros de devolución de llamada relacionados con subprocesos) que tienen alguna medida de vida útil después de ser "atrapados" en el otro lado de la escotilla, pero que no está relacionado con el código de llamada:
Todos queremos que las cosas sean buenas para nosotros. C ++ es para las otras veces.
fuente
unique_ptr<char[]>
se puede usar donde desee el rendimiento de C y la conveniencia de C ++. Considere que necesita operar con millones (ok, miles de millones si aún no confía) de cadenas. Almacenar cada uno de ellos en un objetostring
uvector<char>
objeto separado sería un desastre para las rutinas de administración de memoria (montón). Especialmente si necesita asignar y eliminar diferentes cadenas 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"). Prefieresunique_ptr<char[]> buffer(new char[total_size]);
Por analogía, las mismas consideraciones de rendimiento y conveniencia se aplican a los no
char
datos (considere millones de vectores / matrices / objetos).fuente
vector<char>
? La respuesta, supongo, es porque se inicializarán en cero cuando cree el búfer, mientras que no lo serán si lo usaunique_ptr<char[]>
. Pero esta pepita clave no se encuentra en su respuesta.new[]
std::vector
, por ejemplo, para evitar que los programadores descuidados introduzcan copias accidentalmenteExiste una regla general de que los contenedores de C ++ deben preferirse a los suyos con punteros. Es una regla general; Tiene excepciones. Hay más; Estos son solo ejemplos.
fuente
Si necesita una matriz dinámica de objetos que no sean construibles con copia, entonces un puntero inteligente a una matriz es el camino a seguir. Por ejemplo, ¿qué pasa si necesita una matriz de atómicos?
fuente