Право на перезаписывание std::string нулевого терминатора?

В С++ 11 мы знаем, что std::string гарантированно будет как смежным, так и завершающим нуль (или более педантичным, заканчивается на charT(), который в случае char является нулевым символом 0).

Существует такой API C, который мне нужно использовать, который заполняет строку указателем. Он записывает всю строку + нулевой ограничитель. В С++ 03 мне всегда приходилось использовать vector<char>, потому что я не мог предположить, что string был смежным или завершенным нулем. Но в С++ 11 (предполагая правильно соответствующий basic_string класс, который по-прежнему сохраняется в некоторых стандартных библиотеках), я могу.

Или я могу? Когда я это сделаю:

std::string str(length);

Строка будет выделять length+1 байты, причем последний заполняется нулевым терминатором. Это хорошо. Но когда я передаю это API C, он напишет символы length+1. Он собирается перезаписать нуль-терминатор.

По общему признанию, он собирается перезаписать нуль-терминатор нулевым символом. Коэффициенты хороши, что это сработает (действительно, я не могу себе представить, как он не может работать).

Но мне все равно, что "работает". Я хочу знать, согласно спецификации, нормально ли перезаписывать нулевой ограничитель нулевым символом?

Ответ 1

LWG 2475 сделал это действительным, отредактировав спецификацию operator[](size()) (вставленный текст выделен полужирным шрифтом):

В противном случае возвращает ссылку на объект типа charT со значением charT(), где изменяется объект на любое значение, отличное от charT()приводит к undefined поведению.

Ответ 2

К сожалению, это UB, если я правильно интерпретирую формулировку (в любом случае это не разрешено):

§21.4.5 [string.access] p2

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

(ошибка редакции, в которой говорится T не charT.)

.data() и .c_str() в основном указывают на operator[] (§21.4.7.1 [string.accessors] p1):

Возвращает: указатель p такой, что p + i == &operator[](i) для каждого i в [0,size()].

Ответ 3

Согласно спецификации, перезаписывание завершающего NUL должно быть undefined. Итак, правильная вещь - выделить символы length+1 в строке, передать строковый буфер в C API, а затем resize() вернуться к length:

// "+ 1" to make room for the terminating NUL for the C API
std::string str(length + 1);

// Call the C API passing &str[0] to safely write to the string buffer
...

// Resize back to length
str.resize(length);

(FWIW, я попробовал "перезаписывать NUL" подход на MSVC10, и он отлично работает.)

Ответ 4

Я предполагаю, что n3092 больше не является текущим, а тем, что у меня есть. Раздел 21.4.5 позволяет получить доступ к одному элементу. Для этого требуется pos <= size(). Если pos < size(), то вы получите фактический элемент, в противном случае (например, если pos == size()), вы получите немодифицируемую ссылку.

Я думаю, что в отношении языка программирования вид доступа, который может изменить значение, считается модификацией, даже если новое значение совпадает с старым значением.

Имеет ли g++ библиотеку педантичных ссылок, на которую вы можете ссылаться?