web-dev-qa-db-fra.com

vector :: at vs vector :: operator []

Je sais que at() est plus lent que [] À cause de sa vérification des limites, qui est également discutée dans des questions similaires comme Vecteur C++ à/[] vitesse de l'opérateur ou - :: std :: vector :: at () vs opérateur [] << résultats surprenants !! 5 à 10 fois plus lent/plus rapide! . Je ne comprends tout simplement pas à quoi sert la méthode at().

Si j'ai un vecteur simple comme celui-ci: std::vector<int> v(10); et je décide d'accéder à ses éléments en utilisant at() au lieu de [] Dans la situation où j'ai un index i et je ne sais pas si c'est dans les limites des vecteurs, cela me force à l'envelopper avec un bloc try-catch :

try
{
    v.at(i) = 2;
}
catch (std::out_of_range& oor)
{
    ...
}

bien que je puisse faire le même comportement en utilisant size() et en vérifiant moi-même l'index, ce qui me semble plus facile et très pratique:

if (i < v.size())
    v[i] = 2;

Ma question est donc:
Quels sont les avantages d'utiliser vector :: at par rapport à vector :: operator [] ?
Quand dois-je utiliser vector :: at plutôt que vector :: size + vector :: opérateur [] ?

76
LihO

Je dirais que les exceptions que vector::at() lance ne sont pas vraiment destinées à être capturées par le code immédiatement environnant. Ils sont principalement utiles pour détecter les bogues dans votre code. Si vous devez vérifier les limites au moment de l'exécution, par exemple l'index provient des entrées utilisateur, vous êtes en effet mieux avec une instruction if. Donc, en résumé, concevez votre code avec l'intention que vector::at() ne lève jamais d'exception, de sorte que si c'est le cas, et que votre programme abandonne, c'est le signe d'un bogue. (comme une assert())

59
pmdj

cela me force à l'envelopper avec un bloc try-catch

Non, ce n'est pas le cas (le bloc try/catch peut être en amont). Il est utile lorsque vous souhaitez qu'une exception soit levée plutôt que votre programme pour entrer dans un domaine de comportement non défini.

Je conviens que la plupart des accès hors limites aux vecteurs sont une erreur de programmeur (auquel cas vous devez utiliser assert pour localiser ces erreurs plus facilement; la plupart des versions de débogage des bibliothèques standard le font automatiquement pour vous). Vous ne voulez pas utiliser d'exceptions qui peuvent être avalées en amont pour signaler des erreurs de programmation: vous voulez pouvoir corriger le bug.

Puisqu'il est peu probable qu'un accès hors limites à un vecteur fasse partie du flux de programme normal (dans le cas où c'est le cas, vous avez raison: vérifiez au préalable avec size au lieu de laisser l'exception bouillonner), Je suis d'accord avec votre diagnostic: at est essentiellement inutile.

16
Alexandre C.

Quels sont les avantages d'utiliser vector :: at par rapport à vector :: operator []? Quand dois-je utiliser vector :: at plutôt que vector :: size + vector :: operator []?

Le point important ici est que les exceptions permettent de séparer le flux normal de code de la logique de gestion des erreurs, et un seul bloc de capture peut gérer les problèmes générés à partir d'une myriade de sites de lancement, même s'ils sont dispersés profondément dans les appels de fonction. Donc, ce n'est pas que at() est nécessairement plus facile pour une seule utilisation, mais que cela devient parfois plus facile - et moins obscurcissant la logique du cas normal - lorsque vous avez beaucoup d'indexation à valider.

Il convient également de noter que dans certains types de code, un index est incrémenté de manière complexe et utilisé en permanence pour rechercher un tableau. Dans de tels cas, il est beaucoup plus facile de garantir des vérifications correctes à l'aide de at().

Comme exemple dans le monde réel, j'ai du code qui tokenise C++ en éléments lexicaux, puis un autre code qui déplace un index sur le vecteur de jetons. Selon ce qui s'est produit, je souhaiterais peut-être incrémenter et vérifier l'élément suivant, comme dans:

if (token.at(i) == Token::Keyword_Enum)
{
    ASSERT_EQ(tokens.at(++i), Token::Idn);
    if (tokens.at(++i) == Left_Brace)
        ...
    or whatever

Dans ce genre de situation, il est très difficile de vérifier si vous avez de manière inappropriée atteint la fin de l'entrée, car cela dépend beaucoup des jetons exacts rencontrés. La vérification explicite à chaque point d'utilisation est pénible, et il y a beaucoup plus de place pour les erreurs de programmation comme les incréments pré/post, les décalages au point d'utilisation, le raisonnement erroné sur la validité continue de certains tests antérieurs, etc.

11
Tony Delroy

at peut être plus clair si vous avez un pointeur sur le vecteur:

return pVector->at(n);
return (*pVector)[n];
return pVector->operator[](n);

Mis à part les performances, le premier d'entre eux est le code plus simple et plus clair.

7
Brangdon

Tout d'abord, si at() ou operator[] Est plus lent n'est pas spécifié. Lorsqu'il n'y a pas d'erreur de limites, je m'attends à ce qu'elles soient à peu près à la même vitesse, au moins dans le débogage des versions. La différence est que at() spécifie exactement ce qui se passera là où il y a une erreur de limites (une exception), où comme dans le cas de operator[], C'est un comportement indéfini - un crash dans tous les les systèmes que j'utilise (g ++ et VC++), au moins lorsque les indicateurs de débogage normaux sont utilisés. (Une autre différence est qu'une fois que je suis sûr de mon code, je peux obtenir une augmentation substantielle de la vitesse pour operator[] En désactivant le débogage. Si les performances l'exigent, je ne le ferais pas si cela n'était pas nécessaire) .)

En pratique, at() est rarement approprié. Si le contexte est tel que vous savez que l'index peut être invalide, vous voulez probablement le test explicite (par exemple pour retourner une valeur par défaut ou quelque chose), et si vous savez qu'il ne peut pas être invalide, vous voulez abandonner (et si vous ne savez pas si elle peut être invalide ou non, je vous suggère de spécifier plus précisément l'interface de votre fonction). Il existe cependant quelques exceptions, où l'index non valide peut résulter de l'analyse des données utilisateur, et l'erreur doit entraîner un abandon de la demande entière (mais pas arrêter le serveur); dans de tels cas, une exception est appropriée et at() le fera pour vous.

5
James Kanze

L'intérêt de l'utilisation des exceptions est que votre code de gestion des erreurs peut être plus éloigné.

Dans ce cas spécifique, la saisie utilisateur est en effet un bon exemple. Imaginez que vous vouliez analyser sémantiquement une structure de données XML qui utilise des indices pour faire référence à une sorte de ressource que vous stockez en interne dans un std::vector. Maintenant, l'arborescence XML est une arborescence, donc vous voudrez probablement utiliser la récursivité pour l'analyser. Au fond, dans la récursivité, il peut y avoir une violation d'accès par le rédacteur du fichier XML. Dans ce cas, vous voulez généralement supprimer tous les niveaux de récursivité et simplement rejeter le fichier entier (ou tout type de structure "grossière"). C'est là que cela devient utile. Vous pouvez simplement écrire le code d'analyse comme si le fichier était valide. Le code de la bibliothèque se chargera de la détection des erreurs et vous pouvez simplement attraper l'erreur au niveau grossier.

De plus, d'autres conteneurs, comme std::map, Ont également std::map::at Qui a une sémantique légèrement différente de std::map::operator[]: At peut être utilisé sur une carte const, tandis que operator[] ne peux pas. Maintenant, si vous vouliez écrire du code agnostique de conteneur, comme quelque chose qui pourrait gérer soit const std::vector<T>& Soit const std::map<std::size_t, T>&, ContainerType::at Serait votre arme de choix.

Cependant, tous ces cas apparaissent généralement lors de la manipulation d'une sorte d'entrée de données non validée. Si vous êtes sûr de votre plage valide, comme vous devriez normalement l'être, vous pouvez généralement utiliser operator[], Mais mieux encore, des itérateurs avec begin() et end().

1
ltjax

Selon this article, mis à part les performances, cela ne fait aucune différence d'utiliser at ou operator[], uniquement si l'accès est garanti à l'intérieur de la taille du vecteur. Sinon, si l'accès est uniquement basé sur la capacité du vecteur, il est plus sûr d'utiliser at.

0
ahj

Remarque: Il semble que de nouvelles personnes votent contre cette réponse sans avoir la courtoisie de dire ce qui ne va pas. La réponse ci-dessous est correcte et peut être vérifiée ici .

Il n'y a vraiment qu'une seule différence: at vérifie les limites pendant que operator[] non. Cela s'applique aux versions de débogage ainsi qu'aux versions de version et cela est très bien spécifié par les normes. C'est si simple.

Cela fait de at une méthode plus lente mais c'est aussi un très mauvais conseil de ne pas utiliser at. Vous devez regarder les nombres absolus, pas les nombres relatifs. Je peux parier en toute sécurité que la plupart de votre code effectue des opérations plus coûteuses que at. Personnellement, j'essaie d'utiliser at parce que je ne veux pas qu'un bogue désagréable crée un comportement indéfini et se faufile en production.

0
Shital Shah