Подтвердить что ты не робот

Почему изменение строки через извлеченный указатель на его данные не разрешено?

В С++ 11 символы a std::string должны храниться смежно, как указано в пункте 21.4.1/5:

Объекты char в объекте basic_string сохраняются смежно. То есть для любого объекта basic_string s идентификатор & * (s.begin() + n) == & * s.begin() + n будет иметь место для всех значений n таких, что 0 <= n < lt; s.size().

Однако вот как в § 21.4.7.1 перечислены две функции для получения указателя на базовое хранилище (выделение мое):

const charT * c_str() const noexcept;
const charT * data() const noexcept;
1 Возвращает: Указатель p такой, что p + я == & operator [] (i) для каждого я из [0, size()]. ​​
2 Сложность: постоянное время.
3 Требуется: программа не должна изменять какие-либо значения, хранящиеся в массиве символов.

Одна из возможностей, которую я могу придумать для точки 3, состоит в том, что указатель может стать недействительным при использовании этого объекта (§ 21.4.1/6):

  • как аргумент любой стандартной библиотечной функции, ссылающейся на не-const basic_string как на аргумент.
  • Вызов неконстантных функций-членов, кроме оператора [], спереди, сзади, начала, rbegin, end и разрывать.

Тем не менее, итераторы могут стать недействительными, но мы все равно можем их модифицировать независимо от них. Мы все еще можем использовать указатель, пока он не станет недействительным для чтения из буфера.

Почему мы не можем писать непосредственно в этот буфер? Это потому, что он помещает класс в несогласованное состояние, так как, например, end() не будет обновляться новым концом? Если да, то почему разрешено напрямую писать в буфер нечто вроде std::vector?

Случаи для этого включают возможность передачи буфера std::string в интерфейс C для извлечения строки вместо передачи в vector<char> вместо этого и инициализации строки с помощью итераторов:

std::string text;
text.resize(GetTextLength());
GetText(text.data());
4b9b3361

Ответ 1

Почему мы не можем писать непосредственно в этот буфер?

Я укажу очевидный момент: потому что он const. И отбрасывая значение const, а затем изменяя эти данные... грубо.

Теперь, почему он const? Это происходит в те дни, когда копирование по-записи считалось хорошей идеей, поэтому std::basic_string должно было позволить реализациям поддерживать ее. Было бы очень полезно получить неизменяемый указатель на строку (например, для перехода на C-API), не наносив накладные расходы на копию. Поэтому c_str необходимо вернуть указатель const.

Что до этого еще const? Ну... это идет в странную вещь в стандарте: нулевой терминатор.

Это законный код:

std::string stupid;
const char *pointless = stupid.c_str();

pointless должна быть строкой, завершенной нулем. В частности, это должен быть указатель на символ NUL. Итак, откуда появился символ NUL? Для реализации std::string существует несколько способов реализации этой функции:

  • Используйте мелкоструйную оптимизацию, которая является общей методикой. В этой схеме каждая реализация std::string имеет внутренний буфер, который он может использовать для одного символа NUL.
  • Возвращает указатель на статическую память, содержащую символ NUL. Поэтому каждая реализация std::string возвращает тот же указатель, если это пустая строка.

Всем не нужно принудительно внедрять SSO. Поэтому комитету по стандартам нужен способ сохранить № 2 на столе. И часть этого дает вам строку const из c_str(). И поскольку эта память, скорее всего, реальна const, а не подделка "Пожалуйста, не изменяйте эту память const", давая вам изменяемый указатель на нее, это плохая идея.

Конечно, вы все равно можете получить такой указатель, выполнив &str[0], но в стандарте очень ясно, что изменение терминатора NUL - это плохая идея.

Теперь, если говорить, совершенно верно изменить указатель &str[0] и массив символов в нем. Пока вы остаетесь в полуоткрытом диапазоне [0, str.size()). Вы просто не можете сделать это с помощью указателя, возвращаемого data или c_str. Да, хотя стандарт действительно требует, чтобы str.c_str() == &str[0] был истинным.

Это стандартное для вас.