Доступ к элементам в std :: string, где положение строки больше ее размера

В случае std :: string, если мы обращаемся к элементу, где (element position) == (size of string) стандарт говорит, что он возвращает ссылку на объект типа charT со значением charT().

const_reference operator[](size_type pos) const;
reference       operator[](size_type pos);

Ожидается: pos <= size().

Возвращает: * (begin() + pos), если pos <size(). В противном случае возвращает ссылку на объект типа charT со значением charT(), где изменение объекта на любое значение, отличное от charT(), приводит к неопределенному поведению.

http://eel.is/c++draft/strings#string.access-1

К сожалению, я не мог рассуждать об этом, было бы лучше, если бы это было неопределенное поведение.

Может кто-нибудь объяснить обоснование этого?

Ответы

Ответ 1

Вы должны рассмотреть полные спецификации.

Прежде всего:

Ожидается: pos <= size().

Если вы не следуете предварительному условию, вы все равно будете иметь неопределенное поведение. Сейчас...

Возвращает: * (begin() + pos), если pos <size(). В противном случае возвращает ссылку на объект типа charT со значением charT(), где изменение объекта на любое значение, отличное от charT(), приводит к неопределенному поведению.

Единственный (действительный) случай, на который ссылается "иначе", это когда pos == size(). И это, вероятно, для эмуляции поведения c-строки, имеющего элемент some_string[size] которому можно получить доступ. Обратите внимание, что charT() обычно просто '\0'.

PS: Можно подумать, что для реализации спецификации, operator[] должен проверить, если pos == size. Однако, если базовый массив символов имеет charT() в конце строки, то описанное поведение вы получаете в основном бесплатно. Следовательно, то, что кажется немного отличным от "обычного" доступа к массиву, на самом деле является именно этим.

Ответ 2

Утверждение 1 является предварительным условием для утверждения 2:

  1. Ожидается: pos <= size().

  2. Возвращает: *(begin() + pos) if pos < size().

    В противном случае (поэтому здесь единственно возможная возможность - pos == size()), возвращает ссылку на объект типа charT со значением charT() (то есть '\0'), где изменение объекта на любое значение, отличное от charT() приводит к неопределенному поведению.

str[str.size()] основном указывает на символ нулевого терминатора. Вы можете прочитать и написать это, но вы можете только написать '\0' в него.

Ответ 3

Оператор ожидает, что pos будет меньше или равно size(), поэтому, если оно не меньше, то ожидается, что оно будет равно.

Ответ 4

В дополнение к предыдущим ответам, пожалуйста, посмотрите, что libcxx (реализация llvm) определяет std::string::operator[] как:

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);
}

Посмотрите на .at() который правильно выбрасывает вместо этого.

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];
}

Как вы можете заметить, в первом случае есть assert во время выполнения (спасибо t.niese за указание), который запускается только в режиме отладки, тогда как второй всегда выбрасывает, независимо от параметров сборки библиотеки.