web-dev-qa-db-fra.com

Quand dois-je utiliser std :: map :: at pour récupérer l'élément de la carte

J'ai lu différents articles sur le Web et des questions sur stackoverflow , mais pour moi, il n'est pas clair qu'il existe un cas exclusif quand il vaut mieux utiliser std::map::at Pour récupérer l'élément de la carte.

Selon définition , std::map::at

Renvoie une référence à la valeur mappée de l'élément identifié avec la clé k.

Si k ne correspond à la clé d'aucun élément du conteneur, la fonction lève une exception out_of_range.

Pour moi, seul cas où il vaut la peine d'utiliser std::map::at Lorsque vous êtes sûr à 100% que cet élément avec une clé particulière existe, sinon vous devriez envisager la gestion des exceptions.

  1. Y a-t-il des cas où std::map::at Est considéré comme la manière la plus efficace et élégante de le faire? Dans quels cas recommanderez-vous d'utiliser std::map::at?
  2. Ai-je raison de dire qu'il vaut mieux utiliser map::find() quand il y a une possibilité de ne pas avoir d'élément avec une telle clé? Et map::find() c'est une approche plus rapide et plus élégante?
if ( map.find("key") != map.end() )
{
    // found 

} else
{
    // not found
}

p.s

map::operator[] Peut parfois être dangereux, car si un élément n'existe pas, il l'insérera.

ÉDITÉ: liens en quelque sorte liés lien 1lien 2lienlien 4lien 5lien 6

44
T M

Contrairement à la plupart des réponses existantes ici, notez qu'il existe en fait 4 méthodes liées à la recherche d'un élément dans une carte (en ignorant lower_bound, upper_bound Et equal_range, qui sont moins précis):

  • operator[] N'existe que dans une version non-const, comme indiqué, il créera l'élément s'il n'existe pas
  • at(), introduit en C++ 11, retourne une référence à l'élément s'il existe et lève une exception sinon
  • find() renvoie un itérateur à l'élément s'il existe ou un itérateur à map::end() s'il n'existe pas
  • count() renvoie le nombre de ces éléments, dans un map, c'est 0 ou 1

Maintenant que la sémantique est claire, voyons quand utiliser:

  • si vous souhaitez seulement savoir si un élément est présent dans le map (ou non), alors utilisez count().
  • si vous souhaitez accéder à l'élément, et il doit être dans le map, alors utilisez at().
  • si vous souhaitez accéder à l'élément, et ne savez pas s'il se trouve dans le map ou non, alors utilisez find(); n'oubliez pas de vérifier que l'itérateur résultant n'est pas égal au résultat de end().
  • enfin, si vous souhaitez accéder à l'élément s'il existe ou le créer (et y accéder) dans le cas contraire, utilisez operator[]; si vous ne souhaitez pas appeler le constructeur par défaut de type pour le créer, utilisez alors insert ou emplace de manière appropriée
59
Matthieu M.

std::map::at() lève une exception out_of_range si l'élément est introuvable. Cette exception est une sorte d'exception logic_error Qui pour moi est une sorte de synonyme de assert() du point de vue de l'utilisation: elle doit être utilisée pour signaler des erreurs dans la logique interne du programme, comme violation des conditions préalables logiques ou des invariants de classe.

En outre, vous pouvez utiliser at() pour accéder aux cartes const.

Donc, pour vos questions:

  1. Je recommanderai d'utiliser at() au lieu de [] Lors de l'accès aux const const et lorsque l'absence d'élément est une erreur logique.
  2. Oui, il vaut mieux utiliser map::find() lorsque vous n'êtes pas sûr que l'élément est ici: dans ce cas, ce n'est pas une erreur logique et donc lancer et attraper l'exception std::logic_error Ne sera pas un moyen très élégant de la programmation, même si nous ne pensons pas aux performances.
21
alexeykuzmin0

Comme vous l'avez noté, il existe trois façons d'accéder aux éléments d'une carte: at(), operator[] Et find() (il existe également upper_bound, lower_bound Et equal_range, Mais ce sont pour des circonstances plus compliquées où vous voudrez peut-être trouver un élément suivant/précédent, etc.)

Alors, quand devriez-vous utiliser lequel?

operator[] Est fondamentalement "s'il n'existe pas, créez-en un avec un élément mappé construit par défaut". Cela signifie qu'il ne lancera pas (sauf dans les cas d'angle où l'allocation de mémoire lance ou l'un des constructeurs de clé ou de valeur jette), et vous obtenez certainement une référence à l'élément que vous avez cherché - soit celui existant soit le nouveau créé .

at() lance s'il n'y a pas d'élément pour cette clé. Étant donné que vous ne devez pas utiliser d'exceptions pour le flux de programme normal, l'utilisation de at() signifie "Je suis sûr qu'il existe un tel élément". Mais avec l'avantage supplémentaire que vous obtenez une exception (et non un comportement non défini) si vous vous trompez. Ne l'utilisez pas si vous n'êtes pas certain que l'élément existe.

find() dit "il peut y avoir ou non un tel élément, voyons voir ..." et vous offre la possibilité de réagir différemment dans les deux cas. C'est donc l'approche la plus générale.

12
Arne Mertz

Les 3 de find, operator[] et at sont utiles.

  • find est bon si vous ne voulez pas insérer accidentellement des éléments, mais agissez simplement s'ils existent.

  • at est bon si vous vous attendez à ce que quelque chose se trouve sur une carte et que vous leviez une exception si ce n'était pas le cas. Il peut également accéder aux cartes const de manière plus concise que find (où vous ne pouvez pas utiliser op[])

  • op[] est bon si vous voulez pour insérer un élément par défaut, comme pour le programme de comptage de mots qui met un int 0 pour chaque mot rencontré pour la première fois (avec l'idiome words[Word]++;).

5
Bartek Banachewicz

Cela dépend des exigences de cette fonction et de la façon dont vous structurez le projet. Si vous êtes censé renvoyer un objet et que vous ne pouvez pas le faire car il n'a pas été trouvé, cela vous laisse deux options sur la façon de gérer cela. Vous pouvez par le biais d'une exception ou renvoyer une sorte de sentinelle qui signifie que rien n'a été trouvé. Si vous souhaitez lever une exception, utilisez at() car l'exception sera levée pour vous. Si vous ne voulez pas lever d'exception, utilisez find() pour ne pas avoir à gérer une exception juste pour renvoyer un objet sentinelle.

3
NathanOliver

Je pense que cela dépend de votre cas d'utilisation. Le type de retour de std::map::at() est une référence lvalue à la valeur de l'élément trouvé, tandis que std::map::find() renvoie un itérateur. Vous préférerez peut-être

return myMap.at("asdf"s) + 42;

dans les expressions sur le plus élaboré

return myMap.find("asdf"s)->second + 42;

Chaque fois que vous utilisez le résultat de std::map::at() dans une expression, vous vous attendez à ce que l'élément existe et considérez un élément manquant comme une erreur. Une exception est donc un bon choix pour gérer cela.

3
cdonat

map :: at () renvoie une référence de valeur l, et lorsque vous retournez par référence, vous pouvez utiliser tous ses avantages disponibles tels que le chaînage de méthode.

exemple:

  map<int,typ> table;
  table[98]=a;
  table[99]=b;

  table.at(98)=table.at(99);

operator[] renvoie également la valeur mappée par référence, mais il peut insérer une valeur si la clé recherchée n'est pas trouvée, auquel cas la taille du conteneur augmente d'une unité.

Cela vous oblige à être extrêmement prudent car vous devez prendre soin de invalidation de l'itérateur .

Ai-je raison de dire qu'il vaut mieux utiliser map :: find () quand il y a une possibilité de ne pas avoir d'élément avec une telle clé? Et map :: find () c'est une approche plus rapide et plus élégante?

Oui, sémantiquement, il est logique d'utiliser find () lorsque vous n'êtes pas sûr de l'existence d'un élément. Rend le code plus facile à comprendre même pour un débutant.

En ce qui concerne l'efficacité temporelle, la carte est généralement implémentée comme un arbre RB/un arbre de recherche binaire équilibré et, par conséquent, la complexité est O(logN) pour find ().

Spécifications C++:

T & operator [] (const key_type & x);
Effets: S'il n'y a pas de clé équivalente à x dans la carte, insère value_type (x, T()) dans la carte. Requiert: key_type doit être CopyInsertable et mapped_type doit être DefaultInsertable dans * this. Retourne: Une référence au mapped_type correspondant à x dans * this. 4 Complexité: Logarithmique.

T & at (const key_type & x);
const T & at (const key_type & x) const; Renvoie: Une référence au mapped_type correspondant à x dans * this. Lance: un objet d'exception de type out_of_range si aucun élément de ce type n'est présent. Complexité: logarithmique.

2
basav

Je suppose que la différence est la sémantique.

std::map::at() ressemble à ceci sur ma machine:

mapped_type&
at(const key_type& __k)
{
    iterator __i = lower_bound(__k);
    if (__i == end() || key_comp()(__k, (*__i).first))
        __throw_out_of_range(__N("map::at"));
    return (*__i).second;
}

Comme vous pouvez le voir, il utilise lower_bound, Puis vérifie end(), compare les clés et lève l'exception si nécessaire.

find() ressemble à ceci:

iterator
find(const key_type& __x)
{ return _M_t.find(__x); }

_M_t est un arbre rouge-noir qui stocke les données réelles. De toute évidence, les deux fonctions ont la même complexité (logarithmique). Lorsque vous utilisez find() + recherchez end(), vous faites presque la même chose que at. Je dirais que la différence sémantique est:

  • utilisez at() lorsque vous avez besoin d'un élément à un emplacement spécifique et que vous supposez qu'il est là. Dans ce cas, la situation de l'élément manquant à l'endroit souhaité est exceptionnelle, donc at() lève une exception.
  • utilisez find() lorsque vous devez trouver l'élément dans la carte. Dans ce cas, la situation lorsque l'élément n'est pas présent est normale. Notez également que find() renvoie un itérateur que vous pouvez utiliser à des fins autres que la simple obtention de sa valeur.
2
SingerOfTheFall