Dans les cartes STL, vaut-il mieux utiliser map :: insert que ?

Il y a quelque temps, j’ai discuté avec un collègue de la manière d’insérer des valeurs dans les cartes STL. J’ai préféré map[key] = value; parce qu’il se sent naturel et clair à lire alors qu’il préférait map.insert(std::make_pair(key, value))

Je lui ai juste demandé et aucun de nous ne peut se rappeler la raison pour laquelle l’insertion est meilleure, mais je suis sûr que ce n’était pas simplement une préférence de style plutôt une raison technique telle que l’efficacité. La référence STL SGI dit simplement “Ssortingctement parlant, cette fonction membre est inutile: elle n’existe que par commodité.”

Quelqu’un peut-il me dire cette raison ou est-ce que je rêve simplement qu’il y en a une?

Quand tu écris

 map[key] = value; 

Il n’y a aucun moyen de savoir si vous avez remplacé la value de la key ou si vous avez créé une nouvelle key avec une value .

map::insert() ne créera que:

 using std::cout; using std::endl; typedef std::map MyMap; MyMap map; // ... std::pair res = map.insert(MyMap::value_type(key,value)); if ( ! res.second ) { cout << "key " << key << " already exists " << " with value " << (res.first)->second << endl; } else { cout << "created key " << key << " with value " << value << endl; } 

Pour la plupart de mes applications, je me moque généralement de savoir si je crée ou remplace, donc j'utilise la plus simple pour lire la map[key] = value .

Les deux ont une sémantique différente en ce qui concerne la clé déjà présente dans la carte. Donc, ils ne sont pas directement comparables.

Mais la version de l’opérateur [] nécessite la construction par défaut de la valeur, puis l’assignation, donc si cela coûte plus cher, alors la construction de la copie sera plus coûteuse. Parfois, la construction par défaut n’a pas de sens, et il serait alors impossible d’utiliser la version operator [].

Une autre chose à noter avec std::map :

myMap[nonExistingKey]; créera une nouvelle entrée dans la carte, indexée sur nonExistingKey initialisée à une valeur par défaut.

Cela m’a effrayé la première fois que je l’ai vu (tout en me cognant la tête contre un terrible bug hérité du passé). Je ne m’y serais pas attendu. Pour moi, cela ressemble à une opération d’attaque, et je ne m’attendais pas à un «effet secondaire». Préférez map.find() lorsque vous obtenez de votre carte.

Si la performance du constructeur par défaut n’est pas un problème, veuillez, pour l’amour de Dieu, aller avec la version plus lisible.

🙂

Si la vitesse de votre application est critique, je vous conseillerai d’utiliser l’opérateur [] car il crée un total de 3 copies de l’object d’origine, dont 2 sont des objects temporaires et sont détruits tôt ou tard.

Mais dans insert (), 4 copies de l’object original sont créées dont 3 sont des objects temporaires (pas nécessairement “temporaires”) et sont détruites.

Ce qui signifie un temps supplémentaire pour: 1. Une allocation de mémoire d’un object 2. Un appel de constructeur supplémentaire 3. Un appel de destructeur supplémentaire 4. Une désallocation de mémoire d’un object

Si vos objects sont volumineux, les constructeurs sont typiques, les destructeurs libèrent beaucoup de ressources, les points ci-dessus comptent encore plus. En ce qui concerne la lisibilité, je pense que les deux sont assez justes.

La même question me vint à l’esprit, mais pas de lisibilité mais de rapidité. Voici un exemple de code à travers lequel j’ai appris le point que j’ai mentionné.

 class Sample { static int _noOfObjects; int _objectNo; public: Sample() : _objectNo( _noOfObjects++ ) { std::cout<<"Inside default constructor of object "<<_objectNo< map; map.insert( std::make_pair( 1, sample) ); //map[1] = sample; return 0; } 

Sortie lorsque insert () est utiliséSortie lorsque l'opérateur [] est utilisé

insert est préférable du sharepoint vue de la sécurité des exceptions.

L’expression map[key] = value est en fait deux opérations:

  1. map[key] – crée un élément de carte avec une valeur par défaut.
  2. = value – copie la valeur dans cet élément.

Une exception peut se produire à la deuxième étape. En conséquence, l’opération ne sera que partiellement réalisée (un nouvel élément a été ajouté à map, mais cet élément n’a pas été initialisé avec value ). La situation où une opération n’est pas terminée, mais où l’état du système est modifié, s’appelle l’opération avec “effet secondaire”.

opération d’ insert donne une garantie forte, cela signifie qu’il n’a pas d’effets secondaires ( https://en.wikipedia.org/wiki/Exception_safety ). insert est soit complètement terminé, soit il quitte la carte sans modification.

http://www.cplusplus.com/reference/map/map/insert/ :

Si un seul élément doit être inséré, il n’y a pas de changement dans le conteneur en cas d’exception (garantie forte).

Maintenant, en c ++ 11, je pense que la meilleure façon d’insérer une paire dans une carte STL est la suivante:

 typedef std::map MyMap; MyMap map; auto& result = map.emplace(3,"Hello"); 

Le résultat sera une paire avec:

  • Premier élément (result.first), pointe sur la paire insérée ou pointe sur la paire avec cette clé si la clé existe déjà.

  • Deuxième élément (result.second), true si l’insertion était correcte ou fausse, quelque chose s’est mal passé.

PS: Si vous ne vous préoccupez pas de la commande, vous pouvez utiliser std :: unordered_map;)

Merci!

Un piège avec map :: insert () est qu’il ne remplacera pas une valeur si la clé existe déjà dans la carte. J’ai vu du code C ++ écrit par des programmeurs Java où ils s’attendaient à ce que insert () se comporte de la même manière que Map.put () dans Java où les valeurs sont remplacées.

Une remarque est que vous pouvez également utiliser Boost.Assign :

 using namespace std; using namespace boost::assign; // bring 'map_list_of()' into scope void something() { map my_map = map_list_of(1,2)(2,3)(3,4)(4,5)(5,6); } 

Voici un autre exemple, montrant que operator[] remplace la valeur de la clé si elle existe, mais que .insert pas la valeur si elle existe.

 void mapTest() { map m; for( int i = 0 ; i <= 2 ; i++ ) { pair::iterator,bool> result = m.insert( make_pair( 5, (float)i ) ) ; if( result.second ) printf( "%d=>value %f successfully inserted as brand new value\n", result.first->first, result.first->second ) ; else printf( "! The map already contained %d=>value %f, nothing changed\n", result.first->first, result.first->second ) ; } puts( "All map values:" ) ; for( map::iterator iter = m.begin() ; iter !=m.end() ; ++iter ) printf( "%d=>%f\n", iter->first, iter->second ) ; /// now watch this.. m[5]=900.f ; //using operator[] OVERWRITES map values puts( "All map values:" ) ; for( map::iterator iter = m.begin() ; iter !=m.end() ; ++iter ) printf( "%d=>%f\n", iter->first, iter->second ) ; } 

C’est un cas plutôt restreint, mais à en juger par les commentaires que j’ai reçus, je pense que cela vaut la peine de le noter.

J’ai vu des gens dans le passé utiliser des cartes sous la forme de

 map< const key, const val> Map; 

pour échapper à des cas de réécriture de valeur accidentelle, puis continuer à écrire d’autres bits de code:

 const_cast< T >Map[]=val; 

Leur raison de le faire, car je me souvenais de cela, c’était qu’ils étaient sûrs que dans certains morceaux de code, ils ne remplaceraient pas les valeurs de la carte; par conséquent, aller de l’avant avec la méthode plus «lisible» [] .

Je n’ai jamais eu de problème direct avec le code qui a été écrit par ces personnes, mais je pense fermement jusqu’à aujourd’hui que les risques, même minimes, ne doivent pas être pris lorsqu’ils peuvent être facilement évités.

Dans les cas où vous avez affaire à des valeurs de carte qui ne doivent absolument pas être écrasées, utilisez insert . Ne faites pas d’exceptions simplement pour la lisibilité.

Le fait que la fonction std :: map insert() n’écrase pas la valeur associée à la clé nous permet d’écrire du code d’énumération d’object comme ceci:

 ssortingng word; map dict; while(getline(cin, word)) { dict.insert(make_pair(word, dict.size())); } 

C’est un problème assez courant lorsque nous devons mapper différents objects non uniques sur des identifiants dans la plage 0..N. Ces identifiants peuvent être utilisés ultérieurement, par exemple, dans les algorithmes de graphes. Alternative avec operator[] serait moins lisible à mon avis:

 ssortingng word; map dict; while(getline(cin, word)) { size_t sz = dict.size(); if (!dict.count(word)) dict[word] = sz; }