Acabo de perder tres días de mi vida rastreando un error muy extraño en el que unordered_map :: insert () destruye la variable que inserta. Este comportamiento muy poco obvio ocurre solo en compiladores muy recientes: encontré 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 viera 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 que podría no tener sentido, hasta que examine de cerca los documentos de unordered_map :: insert () en http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/ donde la sobrecarga no 2 es:
template <class P> pair<iterator,bool> insert ( P&& val );
Lo cual es una sobrecarga de movimiento de referencia universal codiciosa, que consume cualquier cosa que no coincida con ninguna de las otras sobrecargas y se mueve construyéndolo en un value_type. Entonces, ¿por qué nuestro código anterior eligió esta sobrecarga y no la sobrecarga unordered_map :: value_type como probablemente la mayoría esperaría?
La respuesta te 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 la destrucción de una variable. Por lo tanto, el comportamiento de destrucción de inserciones es correcto según el estándar C ++ 11, y los compiladores más antiguos eran incorrectos .
Probablemente pueda ver ahora por qué tardé tres días en diagnosticar este error. No era del todo obvio en una base de código grande 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 el typedef era idéntico a value_type.
Entonces, mis preguntas para Stack Overflow:
¿Por qué los compiladores más antiguos no destruyen las variables insertadas como los compiladores más nuevos? Quiero decir, incluso GCC 4.7 no hace esto, y se ajusta bastante a los estándares.
¿Es este problema ampliamente conocido, porque seguramente la actualización de los compiladores hará que el código que solía funcionar deje de funcionar repentinamente?
¿El comité de estándares de C ++ pretendía este comportamiento?
¿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 enviar este comportamiento como una nota N al WG21 y pedirles que implementen un mejor comportamiento.
4.9.0 20131223 (experimental)
respectivamente. La salida es a.second is now 0x2074088
(o similar) para mí.
a
no es normal . Debería hacer una copia. Además, este comportamiento depende totalmente de stdlib, no del compilador.