Je viens de perdre trois jours de ma vie à traquer un bug très étrange où unordered_map :: insert () détruit la variable que vous insérez. Ce comportement très peu évident se produit uniquement dans les compilateurs très récents: j'ai trouvé que clang 3.2-3.4 et GCC 4.8 sont les seuls compilateurs à démontrer cette "fonctionnalité".
Voici un code réduit de ma base de code principale qui illustre le problème:
#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;
}
Comme la plupart des programmeurs C ++, je m'attendrais à ce que la sortie ressemble à quelque chose comme ceci:
a.second is 0x8c14048
a.second is now 0x8c14048
Mais avec clang 3.2-3.4 et GCC 4.8, j'obtiens ceci à la place:
a.second is 0xe03088
a.second is now 0
Ce qui pourrait ne pas avoir de sens, jusqu'à ce que vous examiniez de près la documentation pour unordered_map :: insert () à http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/ où la surcharge n ° 2 est:
template <class P> pair<iterator,bool> insert ( P&& val );
Ce qui est une surcharge de déplacement de référence universelle gourmande, consommant tout ce qui ne correspond à aucune des autres surcharges, et déplacez-le en le construisant dans un value_type. Alors pourquoi notre code ci-dessus a-t-il choisi cette surcharge, et non la surcharge unordered_map :: value_type comme la plupart s'y attendraient probablement?
La réponse vous regarde en face: unordered_map :: value_type est une paire < const int, std :: shared_ptr> et le compilateur penserait correctement qu'une paire < int , std :: shared_ptr> n'est pas convertible. Par conséquent, le compilateur choisit la surcharge de référence universelle de déplacement, et cela détruit l'original, bien que le programmeur n'utilise pas std :: move () qui est la convention typique pour indiquer que vous êtes d'accord avec une variable détruite. Par conséquent, le comportement de destruction d'insert est en fait correct selon la norme C ++ 11 et les anciens compilateurs étaient incorrects .
Vous pouvez probablement voir maintenant pourquoi j'ai mis trois jours pour diagnostiquer ce bogue. Ce n'était pas du tout évident dans une base de code volumineuse où le type inséré dans unordered_map était un typedef défini loin en termes de code source, et personne n'a jamais pensé à vérifier si le typedef était identique à value_type.
Donc mes questions à Stack Overflow:
Pourquoi les anciens compilateurs ne détruisent-ils pas les variables insérées comme les nouveaux compilateurs? Je veux dire, même GCC 4.7 ne le fait pas, et c'est assez conforme aux normes.
Ce problème est-il largement connu, car la mise à niveau des compilateurs entraînera sûrement l'arrêt soudain du code qui fonctionnait auparavant?
Le comité des normes C ++ avait-il l'intention de ce comportement?
Comment suggéreriez-vous que unordered_map :: insert () soit modifié pour donner un meilleur comportement? Je demande cela parce que s'il y a du soutien ici, j'ai l'intention de soumettre ce comportement sous forme de note N au WG21 et de leur demander de mettre en œuvre un meilleur comportement.
4.9.0 20131223 (experimental)
respectivement gcc 4.8.2 et . La sortie est a.second is now 0x2074088
(ou similaire) pour moi.
a
n'est pas le cas. Il devrait en faire une copie. En outre, ce comportement dépend totalement du stdlib, pas du compilateur.