116 votes

Il n’est prévu par le Comité de normalisation de C++ que dans C ++11 unordered_map détruit ce qu’elle insère ?

Résolu: C'est un bug dans libstdc++ < v4.8.2 GCC v4.8 et clang >= v3.2 utilisera si il est présent sur le système. Voir http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57619 pour le rapport. Grâce à Casey et Brian pour donner la bonne réponse. Niall

Question de départ:

Je viens de perdre trois jours de ma vie à la poursuite d'un très étrange d'un bug où les unordered_map::insert() détruit la variable que vous insérez. Ce ne sont vraiment pas évident de comportement se produit dans de très récentes compilateurs seulement: j'ai trouvé que clang 3.2-3.4 et GCC 4.8 sont les seuls compilateurs pour démontrer cette "fonctionnalité".

Voici un code de réduction à partir de mon principale base de code 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;
}

J'ai, comme probablement la plupart des programmeurs C++, attendez la sortie à ressembler à quelque chose comme ceci:

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

Mais avec clang 3.2-3.4 et GCC 4.8-je obtenir ceci à la place:

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

Qui peut faire aucun sens, jusqu'à ce que vous examiner attentivement les docs 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 );

Qui est une gourmande de référence universel déplacer de surcharge, de consommer quelque chose ne correspondant à aucune des autres surcharges, et déplacer la construction dans un value_type. Alors, pourquoi notre code ci-dessus, choisissez cette surcharge, et pas le unordered_map::value_type surcharge comme probablement la plupart serait d'attendre?

La réponse vous regarde en face: unordered_map::value_type est une paire<const int, std::shared_ptr> et le compilateur correctement pense que d'une paire<int, std::shared_ptr> n'est pas convertible. Par conséquent, le compilateur choisit le déplacement de référence universel de surcharge, et qui détruit l'original, malgré le programmeur de ne pas utiliser std::move() qui est typique de la convention pour indiquer que vous êtes d'accord avec une variable détruites. Par conséquent, l'insertion de détruire le comportement est en fait correct , comme par le C++11, et les compilateurs plus âgés étaient incorrectes.

Vous pouvez probablement voir, maintenant, pourquoi j'ai pris trois jours pour diagnostiquer ce bug. Il n'était pas évident dans une grande base de code où le type qui est inséré dans unordered_map était un typedef définie loin dans le code source de termes, et il n'est jamais venu à quiconque de vérifier si la définition de type est identique à value_type.

Donc mes questions à Débordement de Pile:

  1. Pourquoi les compilateurs ne pas détruire les variables insérées comme les nouveaux compilateurs? Je veux dire, même GCC 4.7 ne pas faire cela, et il est assez normes de mise en conformité.

  2. Ce problème est largement connu, parce que sûrement la mise à niveau des compilateurs entraînera le code qui est utilisé pour le travail tout à coup cesser de travailler?

  3. Ne le C++ comité des normes de l'intention de ce comportement?

  4. Comment voulez-vous suggérer que unordered_map::insert() être modifiée pour donner un meilleur comportement? Je pose cette question car si il y a du soutien ici, j'ai l'intention de soumettre ce comportement comme un N remarque pour WG21 et demandez-leur de mettre en œuvre un meilleur comportement.

85voto

Brian Points 15388

Comme d'autres l'ont souligné dans les commentaires, le "universelle" constructeur n'est pas, en fait, doit toujours passer de son argument. Il est censé se déplacer si l'argument est vraiment une rvalue, et copie si c'est une lvalue.

Le comportement, vous observez, qui se déplace toujours, est un bogue dans libstdc++, qui est maintenant fixée selon un commentaire sur la question. Pour les curieux, j'ai pris un coup d'oeil à la g++-4.8 en-têtes.

bits/stl_map.h, lignes 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, lignes 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)); }

Ce dernier est incorrectement à l'aide d' std::move où il devrait être à l'aide de std::forward.

20voto

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

Qui est une gourmande de référence universel déplacer de surcharge, de consommer quelque chose ne correspondant à aucune des autres surcharges, et déplacer la construire dans un value_type.

C'est ce que certains appellent de référence universel, mais c'est vraiment de référence s'effondrer. Dans votre cas, où l'argument est une lvalue de type pair<int,shared_ptr<int>> il va pas entraîner l'argument étant une référence rvalue et il ne doit pas se déplacer.

Alors, pourquoi notre code ci-dessus, choisissez cette surcharge, et pas le unordered_map::value_type surcharge comme probablement la plupart serait d'attendre?

Parce que vous, comme beaucoup d'autres personnes avant, mal interprété l' value_type dans le conteneur. L' value_type de *map (si commandé ou un non ordonnée) pair<const K, T>, ce qui, dans votre cas, est - pair<const int, shared_ptr<int>>. Le type ne correspond pas élimine la surcharge de travail que vous pourriez attendre:

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X