Dans le cas de std :: string, si nous accédons à un élément où (element position) == (size of string)
La norme dit qu'elle renvoie une référence à un objet de type charT
avec la valeur charT()
.
const_reference operator[](size_type pos) const;
reference operator[](size_type pos);
Attend: pos <= size ().
Renvoie: * (begin () + pos) si pos <size (). Sinon, renvoie une référence à un objet de type charT avec la valeur charT (), où la modification de l'objet à une valeur autre que charT () conduit à un comportement non défini.
http://eel.is/c++draft/strings#string.access-1
Malheureusement, je ne pouvais pas raisonner à ce sujet, il aurait été préférable que ce soit un comportement indéfini.
Quelqu'un peut-il expliquer la raison derrière cela?
Vous devez considérer les spécifications complètes.
Tout d'abord:
Attend: pos <= size ().
Si vous ne respectez pas la condition préalable, vous avez de toute façon un comportement indéfini. Maintenant...
Renvoie: * (begin () + pos) si pos <size (). Sinon, renvoie une référence à un objet de type charT avec la valeur charT (), où la modification de l'objet à une valeur autre que charT () conduit à un comportement non défini.
Le seul cas (valide) auquel "sinon" se réfère est lorsque pos == size()
. Et c'est probablement pour émuler le comportement d'une chaîne c qui a un élément some_string[size]
Accessible. Notez que charT()
est généralement juste '\0'
.
PS: On pourrait penser que pour implémenter la spécification, operator[]
Devrait vérifier si pos == size
. Cependant, si le tableau de caractères sous-jacent a une charT()
à la fin de la chaîne, vous obtenez le comportement décrit essentiellement gratuitement. Par conséquent, ce qui semble un peu différent de l'accès "habituel" dans un tableau est en fait juste cela.
La déclaration 1 est la condition préalable à la déclaration 2:
Attend:
pos <= size()
.Renvoie:
*(begin() + pos) if pos < size()
.Sinon ( donc ici la seule possibilité viable est
pos == size()
), renvoie une référence à un objet de tapezcharT
avec la valeurcharT()
(ie'\0'
), où la modification de l'objet à une valeur autre quecharT()
conduit à un comportement indéfini.
str[str.size()]
pointe essentiellement vers le caractère de terminaison nulle. Vous pouvez le lire et l'écrire, mais vous ne pouvez y écrire qu'un '\0'
.
L'opérateur s'attend à ce que pos
soit inférieur ou égal à size()
, donc s'il n'est pas inférieur, il devrait être égal.
En plus des réponses précédentes, veuillez jeter un œil à libcxx
(l'implémentation llvm) définit std::string::operator[]
Comme:
template <class _CharT, class _Traits, class _Allocator>
inline
typename basic_string<_CharT, _Traits, _Allocator>::const_reference
basic_string<_CharT, _Traits, _Allocator>::operator[](size_type __pos) const _NOEXCEPT
{
_LIBCPP_ASSERT(__pos <= size(), "string index out of bounds");
return *(data() + __pos);
}
template <class _CharT, class _Traits, class _Allocator>
inline
typename basic_string<_CharT, _Traits, _Allocator>::reference
basic_string<_CharT, _Traits, _Allocator>::operator[](size_type __pos) _NOEXCEPT
{
_LIBCPP_ASSERT(__pos <= size(), "string index out of bounds");
return *(__get_pointer() + __pos);
}
Jetez un oeil à la .at()
qui jette correctement à la place.
template <class _CharT, class _Traits, class _Allocator>
typename basic_string<_CharT, _Traits, _Allocator>::const_reference
basic_string<_CharT, _Traits, _Allocator>::at(size_type __n) const
{
if (__n >= size())
this->__throw_out_of_range();
return (*this)[__n];
}
Comme vous pouvez, dans le premier cas, il y a une assertion d'exécution (merci t.niese pour l'avoir souligné) qui est déclenchée uniquement en mode débogage alors que la seconde lancera toujours, quelles que soient les options de construction de la bibliothèque.