J'ai identifié quatre façons différentes d'insérer des éléments dans un std::map
:
std::map<int, int> function;
function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));
Laquelle de ces méthodes est la méthode préférée / idiomatique? (Et y a-t-il un autre moyen auquel je n'ai pas pensé?)
Réponses:
Tout d' abord,
operator[]
et lesinsert
fonctions membres ne sont pas fonctionnellement équivalentes:operator[]
va chercher la clé, insérez une construite par défaut valeur si elle est introuvable, et retourner une référence à laquelle vous attribuez une valeur. Évidemment, cela peut être inefficace si lemapped_type
peut bénéficier d'être directement initialisé au lieu d'être construit et attribué par défaut. Cette méthode rend également impossible de déterminer si une insertion a bien eu lieu ou si vous n'avez écrasé que la valeur d'une clé insérée précédemmentinsert
fonction membre n'aura aucun effet si la clé est déjà présente dans la carte et, bien qu'elle soit souvent oubliée, retourne unstd::pair<iterator, bool>
qui peut être intéressant (notamment pour déterminer si l'insertion a effectivement été effectuée).Parmi toutes les possibilités d'appel répertoriées
insert
, les trois sont presque équivalentes. Pour rappel, regardons lainsert
signature dans la norme:Alors, en quoi les trois appels sont-ils différents?
std::make_pair
repose sur la déduction des arguments de modèle et pourrait (et dans ce cas produira ) quelque chose d'un type différent de celui réelvalue_type
de la carte, ce qui nécessitera un appel supplémentaire austd::pair
constructeur de modèle afin de convertir envalue_type
(c'est- à -dire: ajouterconst
àfirst_type
)std::pair<int, int>
nécessitera également un appel supplémentaire au constructeur de modèle destd::pair
afin de convertir le paramètre envalue_type
(c'est- à -dire: ajouterconst
àfirst_type
)std::map<int, int>::value_type
ne laisse absolument aucun doute car il s'agit directement du type de paramètre attendu par lainsert
fonction membre.En fin de compte, j'éviterais d'utiliser
operator[]
lorsque l'objectif est d'insérer, à moins qu'il n'y ait aucun coût supplémentaire dans la construction par défaut et l'attribution dumapped_type
, et que je ne me soucie pas de déterminer si une nouvelle clé a effectivement été insérée. Lors de l'utilisationinsert
, la construction d'unvalue_type
est probablement la voie à suivre.la source
À partir de C ++ 11, vous disposez de deux options supplémentaires majeures. Tout d'abord, vous pouvez utiliser
insert()
avec la syntaxe d'initialisation de liste:Ceci est fonctionnellement équivalent à
mais beaucoup plus concis et lisible. Comme d'autres réponses l'ont noté, cela présente plusieurs avantages par rapport aux autres formes:
operator[]
approche nécessite que le type mappé soit assignable, ce qui n'est pas toujours le cas.operator[]
approche peut écraser des éléments existants et ne vous donne aucun moyen de savoir si cela s'est produit.insert
que vous répertoriez impliquent une conversion de type implicite, ce qui peut ralentir votre code.L'inconvénient majeur est que ce formulaire exigeait que la clé et la valeur soient copiables, donc cela ne fonctionnerait pas avec par exemple une carte avec des
unique_ptr
valeurs. Cela a été corrigé dans la norme, mais le correctif n'a peut-être pas encore atteint votre implémentation de bibliothèque standard.Deuxièmement, vous pouvez utiliser la
emplace()
méthode:Ceci est plus concis que toutes les formes de
insert()
, fonctionne bien avec des types de mouvement uniquement commeunique_ptr
, et théoriquement peut être légèrement plus efficace (bien qu'un compilateur décent devrait optimiser la différence). Le seul inconvénient majeur est que cela peut surprendre un peu vos lecteurs, car lesemplace
méthodes ne sont généralement pas utilisées de cette façon.la source
La première version:
peut ou non insérer la valeur 42 dans la carte. Si la clé
0
existe, elle attribuera 42 à cette clé, écrasant la valeur de cette clé. Sinon, il insère la paire clé / valeur.Les fonctions d'insertion:
par contre, ne faites rien si la clé
0
existe déjà dans la carte. Si la clé n'existe pas, elle insère la paire clé / valeur.Les trois fonctions d'insertion sont presque identiques.
std::map<int, int>::value_type
est letypedef
pourstd::pair<const int, int>
, etstd::make_pair()
produit évidemment unstd::pair<>
magie de déduction via template. Le résultat final, cependant, devrait être le même pour les versions 2, 3 et 4.Lequel utiliserais-je? Je préfère personnellement la version 1; c'est concis et "naturel". Bien sûr, si son comportement d'écrasement n'est pas souhaité, alors je préférerais la version 4, car elle nécessite moins de frappe que les versions 2 et 3. Je ne sais pas s'il existe un moyen unique de facto d'insérer des paires clé / valeur dans un
std::map
.Une autre façon d'insérer des valeurs dans une carte via l'un de ses constructeurs:
la source
Si vous souhaitez écraser l'élément avec la clé 0
Autrement:
la source
Depuis C ++ 17
std::map
propose deux nouvelles méthodes d'insertion:insert_or_assign()
ettry_emplace()
, comme également mentionné dans le commentaire de sp2danny .insert_or_assign()
Fondamentalement,
insert_or_assign()
est une version "améliorée" deoperator[]
. Contrairement àoperator[]
,insert_or_assign()
ne nécessite pas que le type de valeur de la carte soit constructible par défaut. Par exemple, le code suivant ne se compile pas, car ilMyClass
n'a pas de constructeur par défaut:Cependant, si vous remplacez
myMap[0] = MyClass(1);
par la ligne suivante, le code se compile et l'insertion se déroule comme prévu:De plus, similaire à
insert()
,insert_or_assign()
renvoie apair<iterator, bool>
. La valeur booléenne esttrue
si une insertion s'est produite etfalse
si une affectation a été effectuée. L'itérateur pointe vers l'élément qui a été inséré ou mis à jour.try_emplace()
Semblable à ce qui précède,
try_emplace()
est une "amélioration" deemplace()
. Contrairement àemplace()
,try_emplace()
ne modifie pas ses arguments si l'insertion échoue en raison d'une clé déjà existante dans la carte. Par exemple, le code suivant tente de mettre en place un élément avec une clé qui est déjà stockée dans la carte (voir *):Sortie (au moins pour VS2017 et Coliru):
Comme vous pouvez le voir,
pMyObj
ne pointe plus vers l'objet d'origine. Cependant, si vous remplacezauto [it, b] = myMap2.emplace(0, std::move(pMyObj));
par le code suivant, la sortie est différente, car ellepMyObj
reste inchangée:Production:
Code sur Coliru
Remarque: j'ai essayé de garder mes explications aussi courtes et simples que possible pour les intégrer dans cette réponse. Pour une description plus précise et complète, je vous recommande de lire cet article sur Fluent C ++ .
la source
J'ai effectué des comparaisons de temps entre les versions susmentionnées:
Il s'avère que les différences de temps entre les versions d'insert sont minimes.
Cela donne respectivement pour les versions (j'ai exécuté le fichier 3 fois, d'où les 3 décalages horaires consécutifs pour chacune):
2198 ms, 2078 ms, 2072 ms
2290 ms, 2037 ms, 2046 ms
2592 ms, 2278 ms, 2296 ms
2234 ms, 2031 ms, 2027 ms
Par conséquent, les résultats entre différentes versions d'insert peuvent être négligés (cependant, n'a pas effectué de test d'hypothèse)!
La
map_W_3[it] = Widget(2.0);
version prend environ 10 à 15% de temps de plus pour cet exemple en raison d'une initialisation avec le constructeur par défaut pour Widget.la source
En bref, l'
[]
opérateur est plus efficace pour mettre à jour les valeurs car il implique d'appeler le constructeur par défaut du type valeur puis de lui attribuer une nouvelle valeur, tandis queinsert()
est plus efficace pour ajouter des valeurs.L'extrait cité de Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library par Scott Meyers, Item 24 pourrait vous aider.
Vous pouvez décider de choisir une version générique sans programmation de ceci, mais le fait est que je trouve ce paradigme (différencier «ajouter» et «mettre à jour») extrêmement utile.
la source
Si vous voulez insérer un élément dans std :: map - utilisez la fonction insert (), et si vous voulez trouver un élément (par clé) et lui en affecter - utilisez l'opérateur [].
Pour simplifier l'insertion, utilisez la bibliothèque boost :: assign, comme ceci:
la source
Je viens de changer un peu le problème (map of strings) pour montrer un autre intérêt d'insert:
le fait que le compilateur ne montre aucune erreur sur "rancking [1] = 42;" peut avoir un impact dévastateur!
la source
std::string::operator=(char)
existe, mais ils affichent une erreur pour le second car le constructeurstd::string::string(char)
n'existe pas. Cela ne devrait pas produire d'erreur car C ++ interprète toujours librement tout littéral de style entier commechar
, donc ce n'est pas un bogue du compilateur, mais plutôt une erreur de programmeur. En gros, je dis simplement que si cela introduit ou non un bogue dans votre code, vous devez faire attention à vous-même. BTW, vous pouvez imprimerrancking[0]
et un compilateur utilisant ASCII produira*
, ce qui est(char)(42)
.