Безопасно ли использовать std::string::c_str() для изменения базового std::string?

Я наткнулся на некоторый код, который имеет несколько экземпляров приведенного ниже примера:

std::string str = "This  is my string";
char* ptr = const_cast<char*>(str.c_str());

ptr[5] = 'w';
ptr[6] = 'a';

В этом упрощенном примере имеется присвоение std::string::c_str(), которое возвращает const char*, указателю char* с использованием const_cast.

Кажется, это работает так, как задумано, и str изменено соответствующим образом.

Но описание std::string::c_str() в моей местной библиотеке звучит так:

Возвращает константный указатель на содержимое, оканчивающееся нулем. Это дескриптор внутренних данных. Не изменяйте, иначе могут произойти ужасные вещи.

А в cppreference описание включает:

Возвращает указатель на массив символов, заканчивающийся нулем, с данными, эквивалентными тем, которые хранятся в строке.

...

Запись в массив символов, доступ к которому осуществляется через c_str(), является поведением undefined.

В стандартном [string.accessors] описание аналогичное и не предоставляется информация об этом ...массиве символов с завершающим нулем с данными, эквивалентными тем, которые хранятся в строке....

Вместо того, чтобы прояснить проблему, это еще больше меня смутило, какой массив символов? Как это эквивалентно? Это копия? И если да, то почему его модифицируют, а также изменяют исходную строку? Определена ли эта реализация?

Я хотел бы избежать задачи по изменению всех экземпляров кода, где это используется, поэтому мой вопрос:

Есть ли способ исправить это, можно ли использовать указатель ptr для изменения строки str?


person anastaciu    schedule 07.09.2020    source источник
comment
нет, только потому, что он работает, это не означает, что его поведение не является неопределенным, измените c_str на data, чтобы исправить это.   -  person Alan Birtles    schedule 07.09.2020
comment
@AlanBirtles, я тоже так понимаю, но я надеялся, что это можно как-то правильно использовать.   -  person anastaciu    schedule 07.09.2020
comment
В старых стандартах std::string имел гораздо более свободную форму, чем после C++11. Реализации разрешалось делать (но редко) чрезвычайно странные вещи для создания массива символов, возвращаемого c_str.   -  person user4581301    schedule 07.09.2020
comment
@anastaciu Всегда будьте предельно осторожны в том, что вы делаете с const_cast (часто бывает так же плохо, как reinterpret_casr).   -  person πάντα ῥεῖ    schedule 07.09.2020
comment
@ πάνταῥεῖ, да, мои первоначальные инстинкты заключаются в том, чтобы держаться подальше от конструкции такого типа, к сожалению, мне все равно придется иметь с этим дело.   -  person anastaciu    schedule 07.09.2020
comment
Вместо этого используйте str.data(), он возвращает неконстантный указатель. Или &str.front() или &str[0], если вы уверены, что строка не пуста. Все это создает модифицируемый указатель на буфер строки.   -  person Igor Tandetnik    schedule 07.09.2020
comment
@anastaciu Это ответ на ваш вопрос? ? Или это ?   -  person PaulMcKenzie    schedule 07.09.2020
comment
Да по стандарту вполне понятно, что это УБ. Но я бы не ожидал, что это сломается в реальном мире.   -  person HolyBlackCat    schedule 07.09.2020
comment
@IgorTandetnik, да, кажется, это правильный подход.   -  person anastaciu    schedule 07.09.2020
comment
Мех. vector<char> + string_view пока нет   -  person Asteroids With Wings    schedule 07.09.2020
comment
@PaulMcKenzie, более или менее, хотя это хорошие ссылки, в них не упоминается использование c_str.   -  person anastaciu    schedule 07.09.2020
comment
@HolyBlackCat, да, похоже.   -  person anastaciu    schedule 07.09.2020
comment
@AsteroidsWithWings, я доволен самым простым решением, я сожалею, что нашел это в первую очередь :)   -  person anastaciu    schedule 07.09.2020
comment
Ну, я имею в виду в целом :P   -  person Asteroids With Wings    schedule 07.09.2020
comment
@user4581301 user4581301, я пропустил ваш комментарий, спасибо за понимание, в любом случае, с набором опций, предоставленных для того, чтобы сделать это аналогичным, но правильным способом, просто странно, почему это было закодировано именно так.   -  person anastaciu    schedule 07.09.2020


Ответы (1)


Ссылка на c_str, которую вы процитировали, совершенно ясна на иметь значение:

Программа не должна изменять никакие значения, хранящиеся в массиве символов; в противном случае поведение не определено.

Тот факт, что он работает, ничего не значит. Неопределенное поведение означает, что программа может поступить правильно, если захочет.


Если вы хотите изменить базовые данные, вы можете использовать неконстантную перегрузку data(), доступный в C++17, который позволяет изменять все, кроме нулевого терминатора:

Программа не должна изменять значение, хранящееся в p + size(), на любое другое значение, кроме charT(); в противном случае поведение не определено.

person cigien    schedule 07.09.2020
comment
Хорошо подмечено, это кажется хорошим способом, моя цель - как можно меньше возиться с существующим кодом. - person anastaciu; 07.09.2020
comment
Да, простая замена c_str на data должна работать. (хотя я считаю, что это верно только для С++ 17). - person cigien; 07.09.2020
comment
Спасибо, я должен это проверить. - person anastaciu; 07.09.2020
comment
Вы уверены, что? потому что в стандарте четко сказано: изменение массива символов, доступ к которому осуществляется через константную перегрузку данных, имеет неопределенное поведение. Поскольку в С++ 11/14 только data() возвращает const char*, стандарт запрещает использовать data() для изменения базовой строки. - person Dexter; 08.02.2021
comment
@Dexter Если строка не является константой, то вызывается неконстантное наложение data, и я думаю, что изменение содержимого с помощью этого вполне допустимо. - person cigien; 08.02.2021
comment
@cigien, но c++ 11/14 не имеет неконстантной перегрузки data. - person Dexter; 08.02.2021
comment
@Dexter О, я понимаю, что ты имеешь в виду. Хороший вопрос, я отредактирую ответ, чтобы уточнить. - person cigien; 08.02.2021
comment
@cigien, тогда я думаю, что единственным выбором для С++ 11/14 должен быть &str[0]. Может тоже стоит упомянуть :) - person Dexter; 08.02.2021
comment
@Dexter Хм, правда, но я предпочитаю не добавлять решения, которые должны работать со старыми стандартами, если только OP явно не просит об этом. Я думаю, что C++17 достаточно широко распространен, и будущим посетителям не понадобится более старое решение. Я оптимист, YMMV :) - person cigien; 08.02.2021