Ist es beabsichtigt, die von der C++ standards committee", die in C++11 unordered_map zerstört, was es wird?
Ich habe nur verloren drei Tage meines Lebens aufspüren eines sehr seltsamen bug, wo unordered_map::insert() löscht die variable, die Sie einfügen. Dieses hoch nicht-offensichtliche Verhalten tritt in sehr aktuellen Compiler nur: ich fand, dass clang 3.2-3.4 und GCC 4.8 sind die nur Compiler zu demonstrieren, dieses "feature".
Hier einige reduzierte code von meinem Haupt-code-Basis, die zeigt das Problem:
#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, wie wohl die meisten C++ - Programmierer, würde erwarten, dass die Ausgabe so Aussehen:
a.second is 0x8c14048
a.second is now 0x8c14048
Aber mit clang 3.2-3.4 und GCC-4.8 bekomme ich diese statt:
a.second is 0xe03088
a.second is now 0
Die vielleicht keinen Sinn machen, bis Sie genau prüfen die Dokumente für unordered_map::insert() bei http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/, wo keine überlastung 2:
template <class P> pair<iterator,bool> insert ( P&& val );
Ist eine gierige universal Referenzfahrt überlastung, verbrauchen alles, was nicht passend zu jedem der anderen überladungen, und bewegen konstruieren es in einem value_type. Also, warum haben unsere obigen code wählen Sie diese überladung, und nicht die unordered_map::value_type überlast-wie wohl die meisten erwarten würden?
Die Antwort starrt Sie in das Gesicht: unordered_map::value_type ist ein paar<const int, std::shared_ptr> und würde der compiler richtig denke, dass ein paar<int, std::shared_ptr> nicht Cabrio. Also der compiler wählt das verschieben-universal-Referenz-überlastung, und das zerstört die ursprüngliche, trotz der Programmierer nicht mit std::move (), die die typische Konvention für die Anzeige, Sie sind in Ordnung mit einer Variablen immer zerstört. Deshalb legen Sie zerstören Verhalten ist in der Tat richtige wie pro die standard C++11, und ältere Compiler wurden falsche.
Wahrscheinlich können Sie jetzt sehen, warum ich brauchte drei Tage, um dies zu diagnostizieren Fehler. Es war überhaupt nicht klar in eine große code-Basis, wo der Typ wird eingefügt unordered_map war ein typedef definiert, weit Weg im source-code-Bedingungen, und es kam nie zu jemand, um zu überprüfen, ob Sie die Typdefinition war identisch zu value_type.
Also meine Fragen auf Stack Overflow:
-
Warum ältere Compiler, die sich nicht zerstören Variablen eingefügt, wie neuere Compiler? Ich meine, auch GCC 4.7 nicht dies tun, und es ist ziemlich Normen konform.
-
Ist dieses problem allgemein bekannt ist, da sicherlich ein Upgrade-Compiler Ursache code, der verwendet, um die Arbeit zu plötzlich aufhören zu arbeiten?
-
Hast, die C++ standards committee beabsichtigen, dieses Verhalten?
-
Wie würden Sie vorschlagen, dass unordered_map::insert() werden geändert, um ein besseres Verhalten? Ich Frage dies, weil wenn es support hier, ich habe die Absicht zu Unterwerfen, die dieses Verhalten als ein N Hinweis auf WG21 und Sie bitten, zu implementieren, ein besseres Verhalten.
- Nur weil es verwendet eine Universelle ref bedeutet nicht, dass der eingefügte Wert wird immer verschoben - es sollten immer nur so tun, für rvalues, die Ebene
a
ist nicht. Es sollte eine Kopie machen. Auch dieses Verhalten völlig abhängig von der stdlib, nicht der compiler. - Nicht reproduzieren können, mit VS2012.
- Das scheint ein bug in der Implementierung der Bibliothek
- "Deshalb legen Sie zerstören Verhalten ist in der Tat richtig, wie pro die standard C++11, und ältere Compiler waren falsch." Sorry, aber Sie sind falsch. Welcher Teil der C++ - Standard hat man diese Idee aus? BTW cplusplus.com ist nicht offiziell.
- Kann ich nicht reproduzieren, diese auf meinem system, und ich bin mit gcc 4.8.2 und
4.9.0 20131223 (experimental)
beziehungsweise. Ausgabea.second is now 0x2074088
(oder ähnlich) für mich. - Dies war GCC-bug 57619, eine regression in die 4,8-Serie, wurde behoben 4.8.2 im 2013-06.
- Für die Zukunft: wenn überprüft wird, wie Clang funktioniert, verwenden Sie bitte nicht libstdc++ - oder zumindest nicht verwenden, libstdc++ exklusiv. Vermeiden Sie auch sagen, "clang 3.2-3.4", ohne zu erwähnen, dass diejenigen, clangs verwendet libstdc++. Es ist extrem wichtig, wenn über Dinge zu sprechen, die anscheinend zu bugs.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Als andere haben darauf hingewiesen, in den Kommentaren, die "universal" - Konstruktor ist in der Tat nicht, soll immer von seinem argument. Es soll sich zu bewegen, wenn das argument ist wirklich ein rvalue, und kopieren, wenn es ein lvalue.
Das Verhalten, das Sie beobachten, was immer sich bewegt, ist ein bug in der libstdc++, die ist nun behoben, laut einem Kommentar auf die Frage. Für diejenigen neugierig, ich nahm einen Blick auf die g++-4.8 Header.
bits/stl_map.h
Linien 598-603bits/unordered_map.h
Linien 365-370Letzteres ist falsch mit
std::move
wo es sein sollte, mitstd::forward
.libstdc++-v3/include/bits/
. Ich sehe nicht die gleiche Sache. Ich sehe{ return _M_h.insert(std::forward<_Pair>(__x)); }
. Es könnte auch anders sein, für die 4.8, aber ich habe nicht geprüft noch.Das ist, was manche Leute nennen universal Referenz, aber ist wirklich Referenz einstürzenden. In deinem Fall, wo das argument ist ein lvalue Typ
pair<int,shared_ptr<int>>
es wird nicht Ergebnis in das argument ist ein rvalue-Referenz und es sollte nicht von ihm zu bewegen.Weil Sie, wie viele andere Leute vor, falsch interpretiert die
value_type
im container. Dievalue_type
von*map
(ob geordnete oder eine ungeordnete) istpair<const K, T>
, die in Ihrem Fall istpair<const int, shared_ptr<int>>
. Der Typ nicht entspricht, entfällt die überlastung, dass man das vielleicht erwartet hätte:std::move
nicht bewegen, überhaupt nichts.std::forward
zu machen, dass zwicken tun die eigentliche Arbeit... Scott Meyers hat einen guten job gemacht Einstellung ziemlich einfach Regeln für die Weiterleitung (die Verwendung von universal-Referenzen).&&
; Referenz einstürzenden passiert, wenn ein compiler erzeugt eine Vorlage. Referenz einstürzenden ist der Grund, universal-Referenzen funktionieren, aber mein Gehirn nicht, wie wenn man die zwei Begriffe in der gleichen Domäne.const
-zu reduzieren, richtig?template <typename T> void f(const T&);
genanntf<const int>(1)
, und Sie würde nicht rufen, dass universal-const entweder..., Aber ich verstehe, dass dies eine scheint anders aufgrund der Neuheit&&
, und das solange, bis wir mehr, aufteilen in kleinere Häppchen helfen mit kauen Konzepte.