¿Cuál es el capacity()
de un std::vector
que se crea usando el constructor predeterminado? Sé que size()
es cero. ¿Podemos afirmar que un vector construido por defecto no llama asignación de memoria de pila?
De esta manera sería posible crear una matriz con una reserva arbitraria utilizando una sola asignación, como std::vector<int> iv; iv.reserve(2345);
. Digamos que, por alguna razón, no quiero iniciar el size()
2345.
Por ejemplo, en Linux (g ++ 4.4.5, kernel 2.6.32 amd64)
#include <iostream>
#include <vector>
int main()
{
using namespace std;
cout << vector<int>().capacity() << "," << vector<int>(10).capacity() << endl;
return 0;
}
impreso 0,10
. ¿Es una regla o depende del proveedor de STL?
c++
memory-management
stl
vector
No en lista
fuente
fuente
swap
todos los iteradores y las referencias siguen siendo válidas (exceptoend()
s). Eso significa que no es posible un búfer en línea.Respuestas:
El estándar no especifica cuál
capacity
debe ser la inicial de un contenedor, por lo que confía en la implementación. Una implementación común iniciará la capacidad en cero, pero no hay garantía. Por otro lado, no hay forma de mejorar tu estrategia,std::vector<int> iv; iv.reserve(2345);
así que apégate a ella.fuente
vector::reserve
no es lo mismo que especificar un tamaño inicial. Los constructores de vectores que toman un valor de tamaño inicial / copia inicializann
objetos y, por lo tanto, tienen complejidad lineal. OTOH, llamar a la reserva solo significa copiar / moversize()
elementos si se activa una reasignación. En un vector vacío no hay nada que copiar. Por lo tanto, esto último podría ser deseable incluso si la implementación asigna memoria para un vector construido predeterminado.Las implementaciones de almacenamiento de std :: vector varían significativamente, pero todas las que he encontrado comienzan desde 0.
El siguiente código:
#include <iostream> #include <vector> int main() { using namespace std; vector<int> normal; cout << normal.capacity() << endl; for (unsigned int loop = 0; loop != 10; ++loop) { normal.push_back(1); cout << normal.capacity() << endl; } cin.get(); return 0; }
Da el siguiente resultado:
0 1 2 4 4 8 8 8 8 16 16
bajo GCC 5.1 y:
0 1 2 3 4 6 6 9 9 9 13
bajo MSVC 2013.
fuente
Por lo que entendí el estándar (aunque en realidad no podría nombrar una referencia), la instanciación del contenedor y la asignación de memoria se han desacoplado intencionalmente por una buena razón. Por lo tanto, tiene llamadas distintas e independientes para
constructor
para crear el propio contenedorreserve()
para preasignar un bloque de memoria suficientemente grande para acomodar al menos (!) un número determinado de objetosY esto tiene mucho sentido. El único derecho por el que existe
reserve()
es para darle la oportunidad de codificar en torno a reasignaciones posiblemente costosas al hacer crecer el vector. Para que sea útil, debe saber la cantidad de objetos que debe almacenar o, al menos, debe poder hacer una conjetura. Si no se le da esto, es mejor que se mantenga alejado,reserve()
ya que solo cambiará la reasignación por memoria desperdiciada.Así que poniendo todo junto:
reserve()
y no es necesario que esté en el mismo lugar de construcción (por supuesto, podría / debería ser más tarde, después de que se dio cuenta del tamaño requerido para acomodar)reserve()
, ¿no es así?push_back()
, si no lo ha asignado explícitamentereserve()
.Todo esto llega a un funcionamiento completo y una ventaja solo si no lo perturba un constructor de asignación. Tiene valores predeterminados razonables para escenarios comunes que pueden ser anulados a pedido por
reserve()
(yshrink_to_fit()
). Entonces, incluso si el estándar no lo dice explícitamente, estoy bastante seguro de que asumir que un vector recién construido no preasigna es una apuesta bastante segura para todas las implementaciones actuales.fuente
Como una pequeña adición a las otras respuestas, descubrí que cuando se ejecuta en condiciones de depuración con Visual Studio, un vector construido predeterminado aún se asignará en el montón aunque la capacidad comience en cero.
Específicamente si _ITERATOR_DEBUG_LEVEL! = 0, el vector asignará algo de espacio para ayudar con la verificación del iterador.
https://docs.microsoft.com/en-gb/cpp/standard-library/iterator-debug-level
Encontré esto un poco molesto ya que estaba usando un asignador personalizado en ese momento y no esperaba la asignación adicional.
fuente
Esta es una pregunta antigua, y todas las respuestas aquí han explicado correctamente el punto de vista del estándar y la forma en que puede obtener una capacidad inicial de manera portátil usando
std::vector::reserve
;Sin embargo, explicaré por qué no tiene sentido que ninguna implementación de STL asigne memoria al construir un
std::vector<T>
objeto ;std::vector<T>
de tipos incompletos;Antes de C ++ 17, era un comportamiento indefinido construir un
std::vector<T>
si la definición deT
aún se desconoce en el punto de instanciación. Sin embargo, esa restricción se relajó en C ++ 17 .Para asignar memoria de manera eficiente a un objeto, necesita conocer su tamaño. Desde C ++ 17 y posteriores, sus clientes pueden tener casos en los que su
std::vector<T>
clase no conoce el tamaño deT
. ¿Tiene sentido que las características de asignación de memoria dependan de la completitud del tipo?Unwanted Memory allocations
Hay muchas, muchas, muchas veces que necesitará modelar un gráfico en software. (Un árbol es un gráfico); Lo más probable es que lo modele como:
class Node { .... std::vector<Node> children; //or std::vector< *some pointer type* > children; .... };
Ahora piense por un momento e imagine si tuviera muchos nodos terminales. Estaría muy enojado si su implementación de STL asigna memoria adicional simplemente en anticipación de tener objetos
children
.Este es solo un ejemplo, no dude en pensar en más ...
fuente
El estándar no especifica el valor inicial para la capacidad, pero el contenedor STL crece automáticamente para acomodar todos los datos que ingresa, siempre que no exceda el tamaño máximo (use la función miembro max_size para saberlo). Para el vector y la cadena, el crecimiento se gestiona mediante reasignación cada vez que se necesita más espacio. Suponga que desea crear un valor de retención de vector de 1 a 1000. Sin usar la reserva, el código normalmente dará como resultado entre 2 y 18 reasignaciones durante el siguiente ciclo:
vector<int> v; for ( int i = 1; i <= 1000; i++) v.push_back(i);
Modificar el código para usar la reserva puede resultar en 0 asignaciones durante el ciclo:
vector<int> v; v.reserve(1000); for ( int i = 1; i <= 1000; i++) v.push_back(i);
En términos generales, las capacidades de los vectores y las cadenas crecen en un factor de entre 1,5 y 2 cada vez.
fuente