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 i
et 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 [] ?
if (i < v.size()) v[i] = 2;
il existe un chemin de code possible qui n'assigne2
aucun élément dev
. Si c'est le bon comportement, tant mieux. Mais souvent, il n'y a rien de sensé que cette fonction puisse faire quandi >= 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 simplementoperator[]
sans vérification par rapport à la taille, le document quii
doit être dans la plage, et blâment l'UB résultant sur l'appelant.Réponses:
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 uneif
instruction. Donc, en résumé, concevez votre code avec l'intention devector::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 unassert()
)la source
size()
+[]
lorsque l'index dépend de l'entrée des utilisateurs, utiliserassert
dans 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.) .)vector
il est probablement préférable de l'utiliser comme option «juste au cas» plutôt queat()
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.operator[]
, par exemple gcc.gnu.org/onlinedocs/libstdc++/manual/ ... donc si votre plate-forme prend en charge cela, il vaut probablement mieux y aller!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
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 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
size
au lieu de laisser l'exception remonter), je suis d'accord avec votre diagnostic:at
est essentiellement inutile.la source
out_of_range
exception, alorsabort()
est appelé.try..catch
peuvent être présents dans la méthode qui appelle cette méthode.at
est utile dans la mesure où vous vous retrouveriez autrement à écrire quelque chose commeif (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 ".out_of_range
découle delogic_error
, et les autres programmeurs "devraient" savoir mieux que de les attraper enlogic_error
amont et de les ignorer.assert
peut ê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 avecNDEBUG
;-) Chaque mécanisme a ses mérites et ses défauts.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:
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.
la source
at
peut être plus clair si vous avez un pointeur sur le vecteur:Mis à part les performances, le premier d'entre eux est le code plus simple et plus clair.
la source
Premièrement, si
at()
ouoperator[]
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 deoperator[]
, 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 vitesseoperator[]
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 leat()
fera pour vous.la source
operator[]
ne sont pas obligés de vérifier les limites, alors queat()
c'est le cas? Êtes-vous par là des problèmes de mise en cache, de spéculation et de tampon de branchement?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.at()
.std::string
ne fonctionnait pas toujours si les options de vérification ne correspondaient pas à celles du runtime:,-MD
et vous feriez mieux de désactiver la vérification-MDd
, et vous feriez mieux d'avoir sur.)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 unestd::map::at
sémantique légèrement différente destd::map::operator[]
: at peut être utilisé sur une carte const, alors queoperator[]
ne le peut pas. Maintenant, si vous vouliez écrire du code indépendant du conteneur, comme quelque chose qui pourrait traiter soitconst std::vector<T>&
ouconst 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 avecbegin()
etend()
.la source
Selon cet article, mise à part les performances, l'utilisation ne fait aucune différence
at
ouoperator[]
, 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 à utiliserat
.la source
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
at
vérification des limitesoperator[]
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
at
une méthode plus lente mais c'est aussi un très mauvais conseil de ne pas utiliserat
. 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 queat
. Personnellement, j'essaye d'utiliserat
parce que je ne veux pas qu'un vilain bogue crée un comportement indéfini et se faufile en production.la source
std::out_of_range
ou toute forme de lancerstd::logic_error
est, en fait, une erreur de logique en soi ici .at
et[]
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.