vector :: at vs vector :: opérateur []

95

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

Si j'ai un vecteur simple comme celui-ci: std::vector<int> v(10);et que je décide d'accéder à ses éléments en utilisant at()plutôt []qu'en situation lorsque j'ai un index iet que je ne suis pas sûr que ce soit dans les vecteurs limites, cela me force à l' envelopper avec try-catch bloquer :

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

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

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

Ma question est donc la suivante:
quels sont les avantages de l'utilisation de vector :: at sur vector :: operator [] ?
Quand dois-je utiliser vector :: at plutôt que vector :: size + vector :: operator [] ?

LihO
la source
11
+1 très bonne question !! mais je ne pense pas que () est celui couramment utilisé.
Rohit Vipin Mathews
10
Notez que dans votre exemple de code, if (i < v.size()) v[i] = 2;il existe un chemin de code possible qui n'assigne 2aucun élément de v. Si c'est le bon comportement, tant mieux. Mais souvent, il n'y a rien de sensé que cette fonction puisse faire quand i >= v.size(). Il n'y a donc aucune raison particulière pour laquelle il ne devrait pas utiliser d'exception pour indiquer une situation inattendue. De nombreuses fonctions utilisent simplement operator[]sans vérification par rapport à la taille, le document qui idoit être dans la plage, et blâment l'UB résultant sur l'appelant.
Steve Jessop

Réponses:

74

Je dirais que les exceptions qui vector::at()jettent 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 avez besoin de vérifier les limites au moment de l'exécution parce que, par exemple, l'index provient d'une entrée utilisateur, il vaut mieux utiliser une ifinstruction. Donc, en résumé, concevez votre code avec l'intention de vector::at()ne jamais lever d'exception, de sorte que si c'est le cas et que votre programme s'arrête, c'est le signe d'un bogue. (tout comme un assert())

pmdj
la source
1
+1 J'aime l'explication sur la façon de séparer la gestion de la mauvaise entrée de l'utilisateur (validation d'entrée; une entrée invalide peut être attendue, donc n'est pas considérée comme quelque chose d'exceptionnel) ... et les bogues dans le code (le déréférencement de l'itérateur qui est hors de portée est exceptionnel thing)
Bojan Komazec
Donc, vous dites que je devrais utiliser size()+ []lorsque l'index dépend de l'entrée des utilisateurs, utiliser assertdans des situations où l'index ne devrait jamais être hors limites pour une correction facile des bogues à l'avenir et .at()dans toutes les autres situations (juste au cas où, quelque chose de mal pourrait arriver.) .)
LihO
8
@LihO: si votre implémentation propose une implémentation de débogage, vectoril est probablement préférable de l'utiliser comme option «juste au cas» plutôt que at()partout. De cette façon, vous pouvez espérer un peu plus de performances en mode version, juste au cas où vous en auriez besoin.
Steve Jessop
3
Oui, la plupart des implémentations STL de nos jours prennent en charge un mode de débogage qui vérifie même les limites operator[], par exemple gcc.gnu.org/onlinedocs/libstdc++/manual/ ... donc si votre plate-forme prend en charge cela, il vaut probablement mieux y aller!
pmdj
1
@pmdj point fantastique, que je ne connaissais pas ... mais lien orphelin. : P actuel est: gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode.html
underscore_d
16

ça m'oblige à l'envelopper avec un bloc try-catch

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

Je suis d'accord que la plupart des accès hors limites aux vecteurs sont une erreur du programmeur (dans ce cas, vous devez utiliser assertpour 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 programmeur: vous voulez pouvoir corriger le bogue .

Puisqu'il est peu probable qu'un accès hors limites à un vecteur fasse partie du déroulement normal du programme (dans le cas où c'est le cas, vous avez raison: vérifiez à l'avance avec sizeau lieu de laisser l'exception remonter), je suis d'accord avec votre diagnostic: atest essentiellement inutile.

Alexandre C.
la source
Si je n'attrape pas d' out_of_rangeexception, alors abort()est appelé.
LihO
@LihO: Pas nécessairement..les try..catchpeuvent être présents dans la méthode qui appelle cette méthode.
Naveen
12
Si rien d'autre, atest utile dans la mesure où vous vous retrouveriez autrement à écrire quelque chose comme if (i < v.size()) { v[i] = 2; } else { throw what_are_you_doing_you_muppet(); }. Les gens pensent souvent aux fonctions de lancement d'exceptions en termes de "malédictions, je dois gérer l'exception", mais tant que vous documentez soigneusement ce que chacune de vos fonctions peut lancer, elles peuvent également être utilisées comme "génial, je ne le fais pas doivent vérifier une condition et lancer une exception ".
Steve Jessop
@SteveJessop: Je n'aime pas lancer des exceptions pour les bogues de programme, car ils peuvent être interceptés en amont par d'autres programmeurs. Les affirmations sont bien plus utiles ici.
Alexandre C.
6
@AlexandreC. eh bien, la réponse officielle à cela est que cela out_of_rangedécoule de logic_error, et les autres programmeurs "devraient" savoir mieux que de les attraper en logic_erroramont et de les ignorer. assertpeut être ignoré aussi si vos collègues tiennent à ne pas connaître leurs erreurs, c'est juste plus difficile car ils doivent compiler votre code avec NDEBUG;-) Chaque mécanisme a ses mérites et ses défauts.
Steve Jessop
11

Quels sont les avantages de l'utilisation de 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 catch peut gérer les problèmes générés à partir de l'un des innombrables sites de lancement, même s'ils sont dispersés au sein des appels de fonction. Donc, ce n'est pas at()nécessairement plus facile pour une seule utilisation, mais cela devient parfois plus facile - et moins obscurcissant de la logique de 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 d'assurer des vérifications correctes en utilisant at().

En guise d'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. En fonction de ce qui est rencontré, je souhaite 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 atteint la fin de l'entrée de manière inappropriée car cela dépend beaucoup des jetons exacts rencontrés. La vérification explicite à chaque point d'utilisation est douloureuse, et il y a beaucoup plus de place pour l'erreur du programmeur car des incréments pré / post, des décalages au point d'utilisation, un raisonnement erroné sur la validité continue de certains tests antérieurs, etc.

Tony Delroy
la source
10

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.

Brangdon
la source
... en particulier lorsque vous avez besoin d'un pointeur sur le n -ième élément d'un vecteur.
dauphin
4

Premièrement, si at()ou operator[]est plus lent n'est pas spécifié. Lorsqu'il n'y a pas d'erreur de limites, je m'attendrais à ce qu'ils soient à peu près à la même vitesse, au moins dans les versions de débogage. La différence est queat() spécifie exactement ce qui se passera en cas d'erreur de limites (une exception), où, comme dans le cas de operator[], il s'agit d'un comportement indéfini - un plantage dans tous les systèmes que j'utilise (g ++ et VC ++), du 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 operator[] en désactivant le débogage. Si les performances l'exigent, je ne le ferais pas sauf si c'était 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 renvoyer 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 s'il 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 invalide peut résulter de l'analyse des données utilisateur, et l'erreur doit provoquer un abandon de la requête entière (mais pas le serveur vers le bas); dans de tels cas, une exception est appropriée et le at()fera pour vous.

James Kanze
la source
4
Pourquoi vous attendez-vous à ce qu'ils soient à peu près à la même vitesse, alors qu'ils operator[]ne sont pas obligés de vérifier les limites, alors que at()c'est le cas? Êtes-vous par là des problèmes de mise en cache, de spéculation et de tampon de branchement?
Sebastian Mach
@phresnel operator[]n'est pas obligé de vérifier les limites, mais toutes les bonnes implémentations le font. Au moins en mode débogage. La seule différence est ce qu'ils font si l'index est hors limites: operator[]abandonne avec un message d'erreur, at()lève une exception.
James Kanze
2
Désolé, vous avez manqué votre attribut "en mode débogage". Cependant, je ne mesurerais pas le code sur sa qualité en mode débogage. En mode de libération, la vérification n'est requise que par at().
Sebastian Mach
1
@phresnel La plupart du code que j'ai livré a été en mode "débogage". Vous ne désactivez la vérification que lorsque des problèmes de performances l'exigent. (Microsoft pré-2010 était un peu un problème ici, car std::stringne fonctionnait pas toujours si les options de vérification ne correspondaient pas à celles du runtime:, -MDet vous feriez mieux de désactiver la vérification -MDd, et vous feriez mieux d'avoir sur.)
James Kanze
2
Je suis plutôt du camp qui dit «code comme sanctionné (garanti) par la norme»; bien sûr, vous êtes libre de livrer en mode débogage, mais lorsque vous faites du développement multiplateforme (y compris, mais pas exclusivement, le cas du même système d'exploitation, mais des versions de compilateur différentes), se fier au standard est le meilleur choix pour les versions et le mode débogage est considéré comme un outil pour le programmeur pour obtenir cette chose généralement correcte et robuste :)
Sebastian Mach
1

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

Dans ce cas précis, la saisie utilisateur est en effet un bon exemple. Imaginez que vous souhaitiez 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 fichier 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 l'auteur du fichier XML. Dans ce cas, vous voulez généralement sortir de tous les niveaux de récursivité et simplement rejeter le fichier entier (ou toute sorte de structure "plus grossière"). C'est là que cela est 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 d'erreur et vous pouvez simplement attraper l'erreur au niveau grossier.

En outre, d'autres conteneurs, comme std::map, ont également une std::map::atsémantique légèrement différente de std::map::operator[]: at peut être utilisé sur une carte const, alors que operator[]ne le peut pas. Maintenant, si vous vouliez écrire du code indépendant du conteneur, comme quelque chose qui pourrait traiter soit const std::vector<T>&ou const std::map<std::size_t, T>&,ContainerType::at serait votre arme de choix.

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

ltjax
la source
1

Selon cet article, mise à part les performances, l'utilisation ne fait aucune différence atou operator[], uniquement si l'accès est garanti à la taille du vecteur. Sinon, si l'accès est uniquement basé sur la capacité du vecteur, il est plus sûr à utiliser at.

ahj
la source
1
il y a des dragons. que se passe-t-il si nous cliquons sur ce lien? (indice: je le sais déjà, mais sur StackOverflow, nous préférons les commentaires qui ne souffrent pas de pourriture des liens, c'est-à-dire fournissez un bref résumé de ce que vous voulez dire)
Sebastian Mach
Merci pour le conseil. C'est corrigé maintenant.
ahj
0

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: la atvérification des limites operator[]ne l'est pas. Cela s'applique aux versions de débogage ainsi qu'aux versions de version et ceci est très bien spécifié par les normes. C'est si simple.

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

Shital Shah
la source
1
Les exceptions en C ++ sont censées être un mécanisme de gestion des erreurs, pas un outil de débogage. Herb Sutter explique pourquoi jeter std::out_of_rangeou toute forme de lancer std::logic_errorest, en fait, une erreur de logique en soi ici .
Big Temp
@BigTemp - Je ne sais pas comment votre commentaire a été lié à cette question et réponse. Oui, les exceptions sont un sujet très débattu, mais la question ici est la différence entre atet []et ma réponse indique simplement la différence. J'utilise personnellement la méthode "sûre" lorsque la performance n'est pas un problème. Comme le dit Knuth, ne faites pas d'optimisation prématurée. De plus, il est bon de détecter les bogues plus tôt qu'en production, quelles que soient les différences philosophiques.
Shital Shah