std::vector versus std::array en C++
¿ Cuál es la diferencia entre a std::vector
y an std::array
en C++? ¿Cuándo se debe preferir uno sobre otro? ¿Cuáles son los pros y los contras de cada uno? Todo lo que hace mi libro de texto es enumerar en qué se parecen.
std::vector
es una clase de plantilla que encapsula una matriz dinámica 1 , almacenada en el montón, que crece y se reduce automáticamente si se agregan o eliminan elementos. Proporciona todos los ganchos ( begin()
, end()
iteradores, etc.) que hacen que funcione bien con el resto del STL. También tiene varios métodos útiles que le permiten realizar operaciones que en una matriz normal serían engorrosas, como por ejemplo, insertar elementos en medio de un vector (se encarga de todo el trabajo de mover los siguientes elementos detrás de escena).
Dado que almacena los elementos en la memoria asignada en el montón, tiene cierta sobrecarga con respecto a las matrices estáticas.
std::array
es una clase de plantilla que encapsula una matriz de tamaño estático, almacenada dentro del propio objeto, lo que significa que, si crea una instancia de la clase en la pila, la matriz en sí estará en la pila. Su tamaño debe conocerse en el momento de la compilación (se pasa como parámetro de plantilla) y no puede crecer ni reducirse.
Es más limitado que std::vector
, pero a menudo es más eficiente, especialmente para tamaños pequeños, porque en la práctica es principalmente un envoltorio liviano alrededor de una matriz estilo C. Sin embargo, es más seguro, ya que la conversión implícita a puntero está deshabilitada y proporciona gran parte de la funcionalidad relacionada con STL std::vector
y de los otros contenedores, por lo que puede usarlo fácilmente con algoritmos STL y demás. De todos modos, por la limitación misma del tamaño fijo, es mucho menos flexible que std::vector
.
Para obtener una introducción std::array
, consulte este artículo ; Para obtener una introducción rápida a std::vector
las operaciones que son posibles en él, es posible que desee consultar su documentación .
En realidad, creo que en el estándar se describen en términos de complejidad máxima de las diferentes operaciones (por ejemplo, acceso aleatorio en tiempo constante, iteración sobre todos los elementos en tiempo lineal, adición y eliminación de elementos al final en tiempo amortizado constante, etc), pero AFAIK no existe otro método para cumplir dichos requisitos aparte del uso de una matriz dinámica.Como lo indicó @Lucretiel, el estándar en realidad requiere que los elementos se almacenen de forma contigua, por lo que es una matriz dinámica, almacenada donde la coloca el asignador asociado.
Para enfatizar un punto planteado por @MatteoItalia, la diferencia de eficiencia es dónde se almacenan los datos. La memoria de montón (obligatoria con vector
) requiere una llamada al sistema para asignar memoria y esto puede resultar costoso si cuenta ciclos. La memoria de pila (posible para array
) tiene prácticamente "sobrecarga cero" en términos de tiempo, porque la memoria se asigna simplemente ajustando el puntero de la pila y se hace solo una vez al ingresar a una función. La pila también evita la fragmentación de la memoria. Sin duda, std::array
no siempre estará en la pila; Depende de dónde lo asigne, pero aún implicará una asignación de memoria menos del montón en comparación con el vector. Si tienes un
- "matriz" pequeña (por ejemplo, menos de 100 elementos): (una pila típica tiene aproximadamente 8 MB, así que no asigne más de unos pocos KB en la pila o menos si su código es recursivo)
- el tamaño será fijo
- la vida útil está en el alcance de la función (o es un valor de miembro con la misma vida útil que la clase principal)
- estás contando ciclos,
Definitivamente use un std::array
sobre un vector. Si alguno de esos requisitos no se cumple, utilice un archivo std::vector
.
Resumiendo la discusión anterior en una tabla para referencia rápida:
Matriz estilo C | std::matriz | estándar::vector | |
---|---|---|---|
Tamaño | Fijo/Estático | Fijo/Estático | Dinámica |
Eficiencia de la memoria | Más eficiente | Más eficiente | Menos eficiente (puede duplicar su tamaño con una nueva asignación). |
Proceso de copiar | Iterar sobre elementos o utilizar std::copy() |
Copia directa:a2 = a1; |
Copia directa:v2 = v1; |
Pasando a funcionar | Pasado por puntero (tamaño no disponible en la función) o como std::span |
Pasado por valor o como std::span |
Pasado por valor o como std::span |
Tamaño | sizeof a1 / sizeof *a1 o std::size(a1) |
a1.size() o std::size(a1) |
v1.size() o std::size(v1) |
Caso de uso | Para un acceso rápido y cuando las inserciones/eliminaciones no son necesarias con frecuencia. |
Igual que la matriz clásica pero más segura y fácil de pasar y copiar. |
Cuándo pueden ser necesarias adiciones o eliminaciones frecuentes |
Si está considerando utilizar matrices multidimensionales, existe una diferencia adicional entre std::array
y std::vector
. Un multidimensional std::array
tendrá los elementos empaquetados en la memoria en todas las dimensiones, tal como lo es una matriz de estilo C. Un multidimensional std::vector
no estará empaquetado en todas las dimensiones.
Dadas las siguientes declaraciones:
int cConc[3][5];
std::array<std::array<int, 5>, 3> aConc;
int **ptrConc; // initialized to [3][5] via new and destructed via delete
std::vector<std::vector<int>> vConc; // initialized to [3][5]
Un puntero al primer elemento de la matriz de estilo C (cConc) o std::array
(aConc) se puede iterar a través de toda la matriz agregando 1 a cada elemento anterior. Están muy apretados.
Un puntero al primer elemento en la matriz vectorial (vConc) o la matriz de punteros (ptrConc) solo se puede iterar a través de los primeros 5 (en este caso) elementos, y luego hay 12 bytes (en mi sistema) de sobrecarga para el siguiente vector.
Esto significa que una std::vector<std::vector<int>>
matriz inicializada como [3][1000]
matriz tendrá una memoria mucho más pequeña que una inicializada como [1000][3]
matriz, y ambas tendrán una memoria mayor que una std::array
asignada en cualquier sentido.
Esto también significa que no se puede simplemente pasar una matriz de vectores (o punteros) multidimensional a, por ejemplo, OpenGL sin tener en cuenta la sobrecarga de memoria, sino que se puede pasar ingenuamente una matriz multidimensional std::array
a OpenGL y hacer que funcione.
Usando la std::vector<T>
clase:
...es tan rápido como usar matrices integradas, suponiendo que esté haciendo solo las cosas que las matrices integradas le permiten hacer (leer y escribir en elementos existentes).
...cambia de tamaño automáticamente cuando se insertan nuevos elementos.
...le permite insertar nuevos elementos al principio o en el medio del vector, "desplazando" automáticamente el resto de los elementos "hacia arriba" (¿tiene sentido?). También le permite eliminar elementos en cualquier parte del archivo
std::vector
, desplazando automáticamente el resto de los elementos hacia abajo....le permite realizar una lectura de rango verificado con el
at()
método (siempre puede usar los indexadores[]
si no desea que se realice esta verificación).
Hay dos tres advertencias principales al usar std::vector<T>
:
No tiene acceso confiable al puntero subyacente, lo que puede ser un problema si trabaja con funciones de terceros que exigen la dirección de una matriz.
La
std::vector<bool>
clase es tonta. Se implementa como un campo de bits condensado, no como una matriz. ¡Evítalo si quieres una variedad debool
s!Durante el uso,
std::vector<T>
s será un poco más grande que una matriz de C++ con la misma cantidad de elementos. Esto se debe a que necesitan realizar un seguimiento de una pequeña cantidad de información adicional, como su tamaño actual, y porque cada vez questd::vector<T>
cambian el tamaño, reservan más espacio del que necesitan. Esto es para evitar que tengan que cambiar de tamaño cada vez que se inserta un nuevo elemento. Este comportamiento se puede cambiar proporcionando un customallocator
, ¡pero nunca sentí la necesidad de hacerlo!
Editar: después de leer la respuesta de Zud a la pregunta, sentí que debía agregar esto:
La std::array<T>
clase no es lo mismo que una matriz de C++. std::array<T>
es una envoltura muy delgada alrededor de las matrices de C++, con el propósito principal de ocultar el puntero al usuario de la clase (en C++, las matrices se convierten implícitamente como punteros, a menudo con un efecto desalentador). La std::array<T>
clase también almacena su tamaño (longitud), lo que puede resultar muy útil.