Ich habe gerade drei Tage meines Lebens verloren, als ich einen sehr seltsamen Fehler aufgespürt habe, bei dem unordered_map :: insert () die von Ihnen eingefügte Variable zerstört. Dieses höchst nicht offensichtliche Verhalten tritt nur bei neueren Compilern auf: Ich fand, dass Clang 3.2-3.4 und GCC 4.8 die einzigen Compiler sind, die diese "Funktion" demonstrieren.
Hier ist ein reduzierter Code aus meiner Hauptcodebasis, der das Problem demonstriert:
#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;
}
Ich würde, wie wahrscheinlich die meisten C ++ - Programmierer, erwarten, dass die Ausgabe ungefähr so aussieht:
a.second is 0x8c14048
a.second is now 0x8c14048
Aber mit Clang 3.2-3.4 und GCC 4.8 bekomme ich stattdessen Folgendes:
a.second is 0xe03088
a.second is now 0
Was möglicherweise keinen Sinn ergibt , bis Sie die Dokumente auf unordered_map :: insert () unter http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/ genau untersuchen, wobei Überladung Nr. 2 lautet:
template <class P> pair<iterator,bool> insert ( P&& val );
Dies ist eine gierige universelle Referenzverschiebungsüberladung, die alles verbraucht, was nicht mit einer der anderen Überladungen übereinstimmt, und die Konstruktion in einen value_type verschiebt. Warum hat unser Code oben diese Überladung gewählt und nicht die Überladung unordered_map :: value_type, wie es wahrscheinlich die meisten erwarten würden?
Die Antwort starrt Sie ins Gesicht: unordered_map :: value_type ist ein Paar < const int, std :: shared_ptr> und der Compiler würde richtig denken, dass ein Paar < int , std :: shared_ptr> nicht konvertierbar ist. Daher wählt der Compiler die universelle Referenzüberladung für Verschiebungen, die das Original zerstört, obwohl der Programmierer nicht std :: move () verwendet. Dies ist die typische Konvention, um anzuzeigen, dass Sie mit der Zerstörung einer Variablen einverstanden sind. Daher ist das Verhalten zur Zerstörung von Einfügungen gemäß dem C ++ 11-Standard tatsächlich korrekt , und ältere Compiler waren falsch .
Sie können jetzt wahrscheinlich sehen, warum ich drei Tage gebraucht habe, um diesen Fehler zu diagnostizieren. In einer großen Codebasis, in der der in unordered_map eingefügte Typ ein im Quellcode weit entfernt definierter Typedef war, war dies überhaupt nicht offensichtlich, und es kam niemandem in den Sinn, zu überprüfen, ob der Typedef mit value_type identisch war.
Also meine Fragen an Stack Overflow:
Warum zerstören ältere Compiler keine Variablen, die wie neuere Compiler eingefügt wurden? Ich meine, sogar GCC 4.7 macht das nicht und es entspricht ziemlich den Standards.
Ist dieses Problem allgemein bekannt, weil das Aktualisieren von Compilern dazu führt, dass Code, der früher funktionierte, plötzlich nicht mehr funktioniert?
Hat das C ++ - Standardkomitee dieses Verhalten beabsichtigt?
Wie würden Sie vorschlagen, dass unordered_map :: insert () geändert wird, um ein besseres Verhalten zu erzielen? Ich frage dies, denn wenn es hier Unterstützung gibt, beabsichtige ich, dieses Verhalten als N-Hinweis an WG21 zu senden und sie zu bitten, ein besseres Verhalten zu implementieren.
4.9.0 20131223 (experimental)
bzw. Die Ausgabe ist a.second is now 0x2074088
(oder ähnlich) für mich.
a
nicht der Fall ist. Es sollte eine Kopie machen. Außerdem hängt dieses Verhalten vollständig von der stdlib ab, nicht vom Compiler.