¿Es correcto este comportamiento de vector :: resize (size_type n) en C ++ 11 y Boost.Container?

Tengo una aplicación C ++ 03 en la que los tipos std::vector se utilizan como almacenamientos intermedios temporales. Como tal, a menudo se les cambia el tamaño usando std::vector::resize() para garantizar que sean lo suficientemente grandes como para contener los datos requeridos antes de su uso. El prototipo C ++ 03 para esta función es en realidad:

 void resize(size_type n, value_type val = value_type()); 

Entonces, en realidad, cuando se llama a resize() , el vector se amplía agregando la cantidad apropiada de copias de val . A menudo, sin embargo, solo necesito saber que el vector es lo suficientemente grande como para contener los datos que necesito; No lo necesito inicializado con ningún valor. Copiar: construir los nuevos valores es solo una pérdida de tiempo.

C ++ 11 viene al rescate (pensé): en su especificación, divide resize() en dos sobrecargas:

 void resize(size_type n); // value initialization void resize(size_type n, const value_type &val); // initialization via copy 

Esto encaja muy bien con la filosofía de C ++: solo pague por lo que desee. Como noté, sin embargo, mi aplicación no puede usar C ++ 11, así que me alegré cuando encontré la biblioteca Boost.Container, que indica compatibilidad con esta funcionalidad en su documentación. Específicamente, boost::container::vector realidad tiene tres sobrecargas de resize() :

 void resize(size_type n); // value initialization void resize(size_type n, default_init_t); // default initialization void resize(size_type n, const value_type &val); // initialization via copy 

Para verificar que entendí todo, realicé una prueba rápida para verificar el comportamiento de C ++ 11 std::vector y boost::container::vector :

 #include  #include  #include  using namespace std; namespace bc = boost::container; template  void init_vec(VecType &v) { // fill v with values [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] for (size_t i = 0; i < 10; ++i) v.push_back(i); // chop off the end of v, which now should be [1, 2, 3, 4, 5], but the other 5 values // should remain in memory v.resize(5); } template  void print_vec(const char *label, VecType &v) { cout << label << ": "; for (size_t i = 0; i < v.size(); ++i) { cout << v[i] << ' '; } cout << endl; } int main() { // instantiate a vector of each type that we're going to test std::vector std_vec; bc::vector boost_vec; bc::vector boost_vec_default; // fill each vector in the same way init_vec(std_vec); init_vec(boost_vec); init_vec(boost_vec_default); // now resize each vector to 10 elements in ways that *should* avoid reinitializing the new elements std_vec.resize(10); boost_vec.resize(10); boost_vec_default.resize(10, bc::default_init); // print each one out print_vec("std", std_vec); print_vec("boost", boost_vec); print_vec("boost w/default", boost_vec_default); } 

Comstackndo esto con g++ 4.8.1 en el modo C ++ 03 de la siguiente manera:

 g++ vectest.cc ./a.out 

produce el siguiente resultado:

 std: 0 1 2 3 4 0 0 0 0 0 boost: 0 1 2 3 4 0 0 0 0 0 boost w/default: 0 1 2 3 4 5 6 7 8 9 

Esto no es demasiado sorprendente. Espero que el C ++ 03 std::vector inicialice los últimos 5 elementos con ceros. Incluso puedo convencerme de por qué boost::container::vector está haciendo lo mismo (supongo que emula el comportamiento de C ++ 03 en el modo C ++ 03). Solo obtuve el efecto que quería cuando solicité específicamente la inicialización predeterminada. Sin embargo, cuando reconstruí en el modo C ++ 11 de la siguiente manera:

 g++ vectest.cc -std=c++11 ./a.out 

Obtengo estos resultados:

 std: 0 1 2 3 4 0 0 0 0 0 boost: 0 1 2 3 4 0 0 0 0 0 boost w/default: 0 1 2 3 4 5 6 7 8 9 

¡Exactamente lo mismo! Lo que lleva a mi pregunta:

¿Me equivoco al pensar que debería ver los mismos resultados en cada una de las tres pruebas en este caso? Esto parece indicar que el cambio en la interfaz std::vector realidad no ha tenido ningún efecto, ya que los 5 elementos agregados en la llamada final para resize() aún se inicializan con ceros en los dos primeros casos.

No es una respuesta, sino una larga adición a la de Howard : uso un adaptador de asignación que básicamente funciona igual que el asignador de Howard, pero es más seguro desde

  1. solo se interpone en la inicialización de valores y no en todas las inicializaciones,
  2. se inicializa por defecto correctamente.
 // Allocator adaptor that interposes construct() calls to // convert value initialization into default initialization. template > class default_init_allocator : public A { typedef std::allocator_traits a_t; public: template  struct rebind { using other = default_init_allocator< U, typename a_t::template rebind_alloc >; }; using A::A; template  void construct(U* ptr) noexcept(std::is_nothrow_default_constructible::value) { ::new(static_cast(ptr)) U; } template  void construct(U* ptr, Args&&... args) { a_t::construct(static_cast(*this), ptr, std::forward(args)...); } }; 

Hay una pequeña diferencia funcional con las firmas de cambio de resize C ++ 11, pero su prueba no lo expondrá. Considere esta prueba similar:

 #include  #include  struct X { X() {std::cout << "X()\n";} X(const X&) {std::cout << "X(const X&)\n";} }; int main() { std::vector v; v.resize(5); } 

Bajo C ++ 03 esto imprime:

 X() X(const X&) X(const X&) X(const X&) X(const X&) X(const X&) 

Pero bajo C ++ 11 imprime:

 X() X() X() X() X() 

La motivación para este cambio es respaldar mejor los tipos que no se pueden copiar (mover solo) en el vector . La mayoría de las veces, incluido en su caso, este cambio no hace ninguna diferencia.

Hay una forma de lograr lo que desea en C ++ 11 con el uso de un asignador personalizado (que su comstackdor puede o no admitir):

 #include  #include  using namespace std; template  class no_init_alloc : public std::allocator { public: using std::allocator::allocator; template  void construct(U*, Args&&...) {} }; template  void init_vec(VecType &v) { // fill v with values [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] v.resize(10); for (size_t i = 0; i < 10; ++i) v[i] = i; // Note this change!!! // chop off the end of v, which now should be [1, 2, 3, 4, 5], but the other 5 values // should remain in memory v.resize(5); } template  void print_vec(const char *label, VecType &v) { cout << label << ": "; for (size_t i = 0; i < v.size(); ++i) { cout << v[i] << ' '; } cout << endl; } int main() { std::vector> std_vec; init_vec(std_vec); std_vec.resize(10); print_vec("std", std_vec); } 

Que debería salir:

 std: 0 1 2 3 4 5 6 7 8 9 

El no_init_alloc simplemente se niega a hacer cualquier inicialización, lo cual está bien para int , dejándolo con un valor no especificado. Tuve que cambiar tu init_vec para usar la asignación para inicializar en lugar de usar la construcción. Entonces esto puede ser peligroso / confuso si no tienes cuidado. Sin embargo , evita hacer una inicialización innecesaria.

Entonces, en realidad, cuando se llama a resize (), el vector se amplía agregando la cantidad apropiada de copias de val. A menudo, sin embargo, solo necesito saber que el vector es lo suficientemente grande como para contener los datos que necesito; No lo necesito inicializado con ningún valor. Copiar: construir los nuevos valores es solo una pérdida de tiempo.

No en realidad no. Tener un contenedor de elementos que no están realmente construidos no tiene sentido. No estoy seguro de lo que esperabas ver aparte de los ceros. Elementos no especificados / no inicializados? Eso no es lo que significa la inicialización de valor.

Si necesita N elementos, entonces debe tener N elementos construidos adecuadamente, y eso es lo que hace std::vector::resize . La inicialización del valor inicializará por cero un objeto sin un constructor predeterminado para invocar, por lo que realmente es lo contrario de lo que parece querer, que es menos seguridad e inicialización que más.

Sugiero que lo que realmente buscas es std::vector::reserve .

Esto parece indicar que el cambio en la interfaz std::vector realidad no ha tenido ningún efecto

Ciertamente tiene un efecto, simplemente no es el que estás buscando. La nueva sobrecarga de cambio de resize es por conveniencia, por lo que no tiene que construir su propio temporal cuando lo único que necesita es inicialización predeterminada o incluso valor inicial. No es un cambio fundamental en la forma en que funcionan los contenedores, ya que siempre tienen instancias válidas de tipos .

† ¡ Válido pero en un estado no especificado si te mudas de ellos!

Valores no inicializados

Puede haber inicializado el valor creando la clase apropiada. Como el seguiente:

 class uninitializedInt { public: uninitializedInt() {}; uninitializedInt(int i) : i(i) {}; operator int () const { return i; } private: int i; }; 

La salida es idéntica a “boost w / default”.

O crea un asignador personalizado con construct y destroy como nop.

División de resize prototipo

Si void std::vector::resize(size_type n) hace lo que hace void bc::vector::resize(size_type n, default_init_t) , entonces muchos de los viejos códigos válidos se romperían …

Las divisiones de resize() permiten cambiar el tamaño del vector de las clases ‘solo mover’ de la siguiente manera:

 class moveOnlyInt { public: moveOnlyInt() = default; moveOnlyInt(int i) : i(i) {}; moveOnlyInt(const moveOnlyInt&) = delete; moveOnlyInt(moveOnlyInt&&) = default; moveOnlyInt& operator=(const moveOnlyInt&) = delete; moveOnlyInt& operator=(moveOnlyInt&&) = default; operator int () const { return i; } private: int i; }; 

La inicialización del valor de int produce 0.

La inicialización predeterminada de int no inicializa el valor en absoluto; solo conserva todo lo que estaba en la memoria.

O la memoria asignada por resize(10) no fue liberada por resize(5) , o se reutilizó el mismo bloque de memoria. De cualquier manera terminaste con los contenidos anteriores sobrantes.

si quiere usar un vector con el asignador estándar, ¿esto no funciona en C ++ 11?

  namespace{ struct Uninitialised {}; template template std::allocator::construct(U* , Uninitialised&&) { /*do nothing*/ }; } template void resize_uninitialised(std::vector& vec, std::vector::size_type size) { const Uninitialised* p = nullptr; auto cur_size = vec.size(); if(size <= cur_size) return; vec.reserve(size); //this should optimise to vec.m_size += (size - cur_size); //one cannot help thinking there must be simpler ways to do that. vec.insert(vec.end(), p, p + (size - cur_size)); };