Строка:: c_str() больше не завершена нулями в С++ 11?

В С++ 11 basic_string::c_str определяется как точно такое же, как basic_string::data, которое, в свою очередь, определяется как то же самое, что *(begin() + n) и *(&*begin() + n) (когда 0 <= n < size()).

Я не могу найти ничего, что требует, чтобы строка всегда имела нулевой символ в конце.

Означает ли это, что c_str() больше не гарантирует получение строки с нулевым завершением?

Ответ 1

Теперь строки должны использовать буферы с нулевым завершением внутри. Посмотрите на определение operator[] (21.4.5):

Требуется: pos <= size().

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

Оглядываясь назад на c_str (21.4.7.1/1), мы видим, что он определен в терминах operator[]:

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

И оба c_str и data должны быть O (1), поэтому реализация эффективно вынуждена использовать буферы с нулевым завершением.

Кроме того, как отмечает David Rodríguez - dribeas, требование возврата также означает, что вы можете использовать &operator[](0) как синоним для c_str(), поэтому завершающий нулевой символ должен лежать в том же буфере (поскольку *(p + size()) должен быть равен charT()); это также означает, что даже если терминатор инициализируется лениво, невозможно наблюдать буфер в промежуточном состоянии.

Ответ 2

Ну, на самом деле верно, что новый стандарт предусматривает, что .data() и .c_str() теперь являются синонимами. Тем не менее, он не говорит, что .c_str() больше не заканчивается нулем:)

Это просто означает, что теперь вы можете полагаться на .data(), но с нулевым завершением.

Бумага N2668 определяет c_str() и data() элементы std:: basic_string как  следует:

 const charT* c_str() const; 
 const charT* data() const; 

Возвращает: указатель на начальный элемент массива длины  size() + 1, чьи элементы первого размера() равны соответствующим  элементы строки, контролируемые * этим и последним элементом которого является  нулевой символ, заданный charT().

Требуется: программа не должна изменять любые значения, хранящиеся в  массив символов.

Обратите внимание, что это НЕ означает, что любой допустимый std::string можно рассматривать как C-строку, потому что std::string может содержать внедренные нули, что преждевременно заканчивает C-строку при непосредственном использовании в качестве const char *.

Добавление:

У меня нет доступа к фактической опубликованной окончательной спецификации С++ 11, но, похоже, что формулировка была удалена где-то в история пересмотра спецификации: например, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf

§ 21.4.7 операции строки basic_string [string.ops]

§ 21.4.7.1 access_string accessors [string.accessors]

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

Ответ 3

"История" заключалась в том, что давным-давно, когда все работали в одиночных потоках или, по крайней мере, потоки были рабочими со своими собственными данными, они разработали класс строк для С++, который упростил обработку строк, чем раньше, и они перегружали оператор + для конкатенации строк.

Проблема заключалась в том, что пользователи будут делать что-то вроде:

s = s1 + s2 + s3 + s4;

и каждая конкатенация создавала бы временное значение, которое должно было выполнить строку.

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

Это решило проблему выше, но вызвало нагрузку других головных болей, в частности в многопоточном мире, где ожидалось, что операция .c_str() будет доступна только для чтения/ничего не меняет и, следовательно, нет необходимости блокировать что-нибудь. Преждевременная внутренняя блокировка в реализации класса на всякий случай, когда кто-то делал это многопоточное (когда не было даже нитейного стандарта) тоже было не очень хорошо. На самом деле было более дорогостоящим, чем просто копировать буфер каждый раз. По той же причине реализация "copy on write" была оставлена ​​для реализации строк.

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

Ответ 4

Хорошо заметили. Это, безусловно, является недостатком в недавно принятом стандарте; Я уверен, что не было никакого намерения разорвать весь код, использующий в настоящее время c_str. Я бы предложил отчет о дефекте или, по крайней мере, задал вопрос в comp.std.c++ (который, как правило, заканчивается перед комитетом, если он касается дефекта).