push_back emplace_back c++ c++11 vector stl

emplace_back - c++ emplace back



¿Puede std:: vector emplace_back copiar la construcción de un elemento del vector en sí? (3)

Al contrario de lo que algunas otras personas han escrito aquí, esta semana hice la experiencia de que esto no es seguro , al menos cuando trato de tener un código portátil con un comportamiento definido.

Aquí hay un código de ejemplo que puede exponer el comportamiento indefinido:

std::vector<uint32_t> v; v.push_back(0); // some more push backs into v followed but are not shown here... v.emplace_back(v.back()); // problem is here!

El código anterior se ejecutó en Linux con un STL de g ++ sin problemas.

Al ejecutar el mismo código en Windows (compilado con Visual Studio 2013 Update5), el vector a veces contenía algunos elementos distorsionados (aparentemente valores aleatorios).

La razón es que la referencia devuelta por v.back() se invalidó debido a que el contenedor alcanzó su límite de capacidad dentro de v.emplace_back() , antes de que el elemento se añadiera al final.

emplace_back() implementación STL de emplace_back() VC ++ y pareció asignar nuevo almacenamiento, copiar los elementos vectoriales existentes en la nueva ubicación de almacenamiento, liberar el almacenamiento anterior y luego construir el elemento al final del nuevo almacenamiento. En ese punto, la memoria subyacente del elemento al que se hace referencia puede haberse liberado o invalidado. Eso producía un comportamiento indefinido, provocando que los elementos vectoriales insertados en los umbrales de reasignación se distorsionaran.

Esto parece ser un error (aún no fijado) en Visual Studio . Con otras implementaciones de STL que probé, el problema no ocurrió.

Al final, debe evitar pasar una referencia a un elemento vectorial al emplace_back() del mismo vector por ahora, al menos si su código se compila con Visual Studio y se supone que debe funcionar.

Cuando uso push_back de std::vector , puedo presionar un elemento del vector en sí mismo sin temor a invalidar el argumento debido a la reasignación:

std::vector<std::string> v = { "a", "b" }; v.push_back(v[0]); // This is ok even if v.capacity() == 2 before this call.

Sin embargo, al usar emplace_back , std::vector reenvía el argumento al constructor de std::string para que la construcción de la copia ocurra en el vector. Esto me hace sospechar que la reasignación del vector ocurre antes de que se construya la nueva cadena (de lo contrario no se asignaría en el lugar), invalidando así el argumento antes de su uso.

¿Esto significa que no puedo agregar un elemento del vector en sí mismo con emplace_back , o tenemos algún tipo de garantía en caso de reasignación, similar a push_back ?

En codigo:

std::vector<std::string> v = { "a", "b" }; v.emplace_back(v[0]); // Is this valid, even if v.capacity() == 2 before this call?


Revisé mi implementación vectorial y funciona aquí de la siguiente manera:

  1. Asignar nueva memoria
  2. Objeto Emplace
  3. Dealloc vieja memoria

Entonces todo está bien aquí. Una implementación similar se utiliza para push_back por lo que este es bien dos.

FYI, aquí está la parte relevante de la implementación. He agregado comentarios:

template<typename _Tp, typename _Alloc> template<typename... _Args> void vector<_Tp, _Alloc>:: _M_emplace_back_aux(_Args&&... __args) { const size_type __len = _M_check_len(size_type(1), "vector::_M_emplace_back_aux"); // HERE WE DO THE ALLOCATION pointer __new_start(this->_M_allocate(__len)); pointer __new_finish(__new_start); __try { // HERE WE EMPLACE THE ELEMENT _Alloc_traits::construct(this->_M_impl, __new_start + size(), std::forward<_Args>(__args)...); __new_finish = 0; __new_finish = std::__uninitialized_move_if_noexcept_a (this->_M_impl._M_start, this->_M_impl._M_finish, __new_start, _M_get_Tp_allocator()); ++__new_finish; } __catch(...) { if (!__new_finish) _Alloc_traits::destroy(this->_M_impl, __new_start + size()); else std::_Destroy(__new_start, __new_finish, _M_get_Tp_allocator()); _M_deallocate(__new_start, __len); __throw_exception_again; } std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish, _M_get_Tp_allocator()); // HERE WE DESTROY THE OLD MEMORY _M_deallocate(this->_M_impl._M_start, this->_M_impl._M_end_of_storage - this->_M_impl._M_start); this->_M_impl._M_start = __new_start; this->_M_impl._M_finish = __new_finish; this->_M_impl._M_end_of_storage = __new_start + __len; }


emplace_back se requiere para estar seguro por la misma razón que se requiere push_back para estar seguro ; la invalidación de punteros y referencias solo tiene efecto una vez que la llamada al método de modificación retorna.

En la práctica, esto significa que emplace_back realizando una reasignación es necesario para proceder en el siguiente orden (ignorando el manejo de errores):

  1. Asignar nueva capacidad
  2. Emplace-construir nuevo elemento al final del nuevo segmento de datos
  3. Mover-construir elementos existentes en el nuevo segmento de datos
  4. Destruir y desasignar el segmento de datos antiguos

En este hilo de reddit, STL reconoce que el VC11 no admite v.emplace_back(v[0]) como un error, por lo que debería verificar si su biblioteca admite este uso y no lo da por hecho.

Tenga en cuenta que algunas formas de autoinserción están específicamente prohibidas por el Estándar; por ejemplo en [sequence.reqmts] paragraph 4 Table 100 a.insert(p,i,j) tiene el requisito previo " i y j no son iteradores en a ".