Il y a quelque temps, j'ai eu une discussion avec un collègue sur la façon d'insérer des valeurs dans les cartes STL . J'ai préféré
map[key] = value;
parce que ça semble naturel et clair à lire alors qu'il préférait
map.insert(std::make_pair(key, value))
Je viens de lui demander et aucun de nous ne peut se souvenir de la raison pour laquelle l'insert est meilleur, mais je suis sûr que ce n'était pas seulement une préférence de style mais plutôt une raison technique comme l'efficacité. La référence SGI STL dit simplement "Strictement parlant, cette fonction membre n'est pas nécessaire: elle n'existe que par commodité."
Quelqu'un peut-il me dire cette raison, ou est-ce que je rêve juste qu'il y en ait une?
Réponses:
Quand tu écris
il n'y a aucun moyen de savoir si vous avez remplacé le
value
forkey
ou si vous en avez créé un nouveaukey
avecvalue
.map::insert()
ne fera que créer:Pour la plupart de mes applications, je ne me soucie généralement pas de créer ou de remplacer, donc j'utilise le plus facile à lire
map[key] = value
.la source
(res.first)->second
plutôt quevalue
dans le deuxième cas.else
car je pense que l'utilisationvalue
est plus claire que l'itérateur. Ce n'est que si le type de la valeur avait une copie inhabituelle ctor ou op == que ce serait différent, et que ce type entraînerait d'autres problèmes en utilisant des conteneurs STL comme map.map.insert(std::make_pair(key,value))
devrait êtremap.insert(MyMap::value_type(key,value))
. Le type renvoyémake_pair
ne correspond pas au type pris parinsert
et la solution actuelle nécessite une conversionoperator[]
, comparez simplement la taille avant et après. Imho étant en mesure d'appelermap::operator[]
uniquement pour les types constructibles par défaut est beaucoup plus important.Les deux ont une sémantique différente en ce qui concerne la clé déjà existante dans la carte. Ils ne sont donc pas vraiment directement comparables.
Mais la version de l'opérateur [] nécessite la construction par défaut de la valeur, puis l'attribution, donc si c'est plus cher que la construction de copie, alors ce sera plus cher. Parfois, la construction par défaut n'a pas de sens, et il serait alors impossible d'utiliser la version opérateur [].
la source
Une autre chose à noter avec
std::map
:myMap[nonExistingKey];
créera une nouvelle entrée dans la carte, codée pournonExistingKey
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 bug méchant). Je ne m'y serais pas attendu. Pour moi, cela ressemble à une opération get, et je ne m'attendais pas à «l'effet secondaire». Préférez
map.find()
lors de l'obtention de votre carte.la source
Si les performances du constructeur par défaut ne sont pas un problème, veuillez, pour l'amour de Dieu, opter pour la version plus lisible.
:)
la source
insert
est meilleur du point de vue de la sécurité d'exception.L'expression
map[key] = value
est en fait deux opérations:map[key]
- création d'un élément de carte avec la valeur par défaut.= value
- copier 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 effectuée (un nouvel élément a été ajouté à la carte, mais cet élément n'a pas été initialisé avec
value
). La situation où une opération n'est pas terminée, mais l'état du système est modifié, est appelée l'opération avec «effet secondaire».insert
l'opération donne une forte garantie, signifie qu'elle n'a pas d'effets secondaires ( https://en.wikipedia.org/wiki/Exception_safety ).insert
est soit complètement terminé, soit il laisse la carte dans un état non modifié.http://www.cplusplus.com/reference/map/map/insert/ :
la source
Si votre application est critique en termes de vitesse, je vous conseillerai d'utiliser l'opérateur [] car elle crée au total 3 copies de l'objet d'origine dont 2 sont des objets temporaires et tôt ou tard détruits au fur et à mesure.
Mais dans l'insert (), 4 copies de l'objet d'origine sont créées dont 3 sont des objets temporaires (pas nécessairement "temporaires") et sont détruites.
Ce qui signifie du temps supplémentaire pour: 1. Une allocation de mémoire d'objets 2. Un appel de constructeur supplémentaire 3. Un appel de destructeur supplémentaire 4. Une désallocation de mémoire d'objets
Si vos objets sont grands, les constructeurs sont typiques, les destructeurs font beaucoup de libération 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 m'est venue à l'esprit mais pas la lisibilité mais la vitesse. Voici un exemple de code grâce auquel j'ai appris le point que j'ai mentionné.
la source
insert
doit faire la même recherche, donc aucune différence dans celle de[]
(car les clés de carte sont uniques).Maintenant, en c ++ 11, je pense que la meilleure façon d'insérer une paire dans une carte STL est:
Le résultat sera une paire avec:
Premier élément (result.first), pointe vers la paire insérée ou pointe vers la paire avec cette clé si la clé existe déjà.
Deuxième élément (result.second), vrai si l'insertion était correcte ou fausse si quelque chose s'est mal passé.
PS: Si vous ne vous souciez pas de la commande, vous pouvez utiliser std :: unordered_map;)
Merci!
la source
Un gotcha 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 () en Java où les valeurs sont remplacées.
la source
Une remarque est que vous pouvez également utiliser Boost .
la source
Voici un autre exemple, montrant que
operator[]
remplace la valeur de la clé si elle existe, mais.insert
ne remplace pas la valeur si elle existe.la source
C'est un cas assez restreint, mais à en juger par les commentaires que j'ai reçus, je pense que cela vaut la peine d'être noté.
J'ai déjà vu des gens utiliser des cartes sous forme de
pour éviter les cas d'écrasement accidentel de valeur, mais continuez à écrire dans d'autres bits de code:
Si je me souviens bien, c'est parce qu'ils étaient sûrs que dans ces certains morceaux de code, ils n'écraseraient pas les valeurs de la carte; par conséquent, aller de l'avant avec la méthode plus «lisible»
[]
.En fait, 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 - aussi petits soient-ils - ne devraient 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é.la source
insert
(notinput
), car leconst_cast
fera écraser toute valeur précédente, ce qui est très non constant. Ou, ne marquez pas le type de valeur commeconst
. (Ce genre de chose est généralement le résultat ultime deconst_cast
, donc c'est presque toujours un drapeau rouge indiquant une erreur ailleurs.)insert
dans les cas où vous souhaitez empêcher l'écrasement des valeurs. (Vient de changer leinput
eninsert
- merci)const_cast<T>(map[key])
étaient 1. [] est plus lisible, 2. ils ont confiance en certains morceaux de code, ils ne remplaceront pas les valeurs, et 3. ils ne le font pas veulent que d'autres bits de code inconnu écrasent leurs valeurs - d'où leconst value
.const_cast
semble plus que nier la "lisibilité" supplémentaire de[]
, et ce genre de confiance est presque suffisant pour licencier un développeur. Les conditions d'exécution difficiles sont résolues par des conceptions pare-balles, et non par des sentiments intestinaux.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'objet comme ceci:C'est un problème assez courant lorsque nous devons mapper différents objets non uniques à certains identifiants dans la plage 0..N. Ces identifiants peuvent être utilisés ultérieurement, par exemple, dans des algorithmes de graphe. Une alternative avec
operator[]
semblerait moins lisible à mon avis:la source