compiler compilador c++ gcc c++11 clang standards

compilador - gcc c++



¿El comité de estándares de C++ pretende que en C++ 11 unordered_map destruya lo que inserta? (2)

Como otros han señalado en los comentarios, no se supone que el constructor "universal" siempre se mueva de su argumento. Se supone que debe moverse si el argumento es realmente un valor r, y copia si es un valor l.

El comportamiento, observa, que siempre se mueve, es un error en libstdc ++, que ahora se corrige de acuerdo con un comentario sobre la pregunta. Para los curiosos, eché un vistazo a los encabezados g ++ - 4.8.

bits/stl_map.h , líneas 598-603

template<typename _Pair, typename = typename std::enable_if<std::is_constructible<value_type, _Pair&&>::value>::type> std::pair<iterator, bool> insert(_Pair&& __x) { return _M_t._M_insert_unique(std::forward<_Pair>(__x)); }

bits/unordered_map.h , líneas 365-370

template<typename _Pair, typename = typename std::enable_if<std::is_constructible<value_type, _Pair&&>::value>::type> std::pair<iterator, bool> insert(_Pair&& __x) { return _M_h.insert(std::move(__x)); }

El último está usando incorrectamente std::move donde debería estar usando std::forward .

Resuelto: Este es un error en libstdc ++ <v4.8.2 que usarán GCC v4.8 y clang> = v3.2 si está presente en el sistema. Consulte http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57619 para obtener el informe. Gracias a Casey y Brian por dar la respuesta correcta. Niall

Pregunta original:

Acabo de perder tres días de mi vida rastreando un error muy extraño donde unordered_map :: insert () destruye la variable que inserta. Este comportamiento altamente no obvio solo ocurre en compiladores muy recientes: descubrí que clang 3.2-3.4 y GCC 4.8 son los únicos compiladores que demuestran esta "característica".

Aquí hay un código reducido de mi base de código principal que demuestra el problema:

#include <memory> #include <unordered_map> #include <iostream> int main(void) { std::unordered_map<int, std::shared_ptr<int>> map; auto a(std::make_pair(5, std::make_shared<int>(5))); std::cout << "a.second is " << a.second.get() << std::endl; map.insert(a); // Note we are NOT doing insert(std::move(a)) std::cout << "a.second is now " << a.second.get() << std::endl; return 0; }

Yo, como probablemente la mayoría de los programadores de C ++, esperaría que la salida se vea así:

a.second is 0x8c14048 a.second is now 0x8c14048

Pero con clang 3.2-3.4 y GCC 4.8 obtengo esto en su lugar:

a.second is 0xe03088 a.second is now 0

Lo cual puede no tener sentido, hasta que examine de cerca los documentos para unordered_map :: insert () en http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/ donde overload no 2 es:

template <class P> pair<iterator,bool> insert ( P&& val );

Que es una avasalladora sobrecarga de movimiento de referencia universal, que consume cualquier cosa que no concuerde con ninguna de las otras sobrecargas, y se mueve construyéndola en un value_type. Entonces, ¿por qué nuestro código anterior eligió esta sobrecarga y no la sobrecarga de unordered_map :: value_type como probablemente la mayoría esperaría?

La respuesta lo mira a la cara: unordered_map :: value_type es un par < const int, std :: shared_ptr> y el compilador pensaría correctamente que un par < int , std :: shared_ptr> no es convertible. Por lo tanto, el compilador elige la sobrecarga de referencia universal de movimiento, y eso destruye el original, a pesar de que el programador no usa std :: move (), que es la convención típica para indicar que está de acuerdo con que se destruya una variable. Por lo tanto, el comportamiento de destrucción de inserción es, de hecho, correcto según el estándar C ++ 11, y los compiladores anteriores eran incorrectos .

Probablemente puedas ver ahora por qué tardé tres días en diagnosticar este error. No era del todo obvio en una gran base de código donde el tipo que se insertaba en unordered_map era un typedef definido muy lejos en términos de código fuente, y nunca se le ocurrió a nadie verificar si typedef era idéntico a value_type.

Entonces mis preguntas para Stack Overflow:

  1. ¿Por qué los compiladores más antiguos no destruyen las variables insertadas como compiladores más nuevos? Quiero decir, incluso GCC 4.7 no hace esto, y cumple con los estándares.

  2. ¿Este problema es ampliamente conocido, porque seguramente la actualización de compiladores hará que el código que solía funcionar deje de funcionar repentinamente?

  3. ¿El comité de estándares de C ++ intentó este comportamiento?

  4. ¿Cómo sugeriría que unordered_map :: insert () se modifique para ofrecer un mejor comportamiento? Pregunto esto porque si hay apoyo aquí, tengo la intención de presentar este comportamiento como una nota N para WG21 y pedirles que implementen un mejor comportamiento.


template <class P> pair<iterator,bool> insert ( P&& val );

Que es una avasalladora sobrecarga de movimiento de referencia universal, que consume cualquier cosa que no concuerde con ninguna de las otras sobrecargas, y se mueve construyéndola en un value_type.

Eso es lo que algunas personas llaman referencia universal , pero realmente es un colapso de referencia . En su caso, donde el argumento es un lvalue de tipo pair<int,shared_ptr<int>> no dará como resultado que el argumento sea una referencia de valor r ni se moverá de él.

Entonces, ¿por qué nuestro código anterior eligió esta sobrecarga y no la sobrecarga de unordered_map :: value_type como probablemente la mayoría esperaría?

Porque usted, como muchas otras personas antes, malinterpretó el value_type en el contenedor. El value_type de *map (ya sea ordenado o no) es pair<const K, T> , que en su caso es pair<const int, shared_ptr<int>> . El tipo que no coincide elimina la sobrecarga que podría estar esperando:

iterator insert(const_iterator hint, const value_type& obj);