Является ли string :: c_str () больше не завершается нулем в C ++ 11? - PullRequest
72 голосов
/ 26 сентября 2011

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

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

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

Ответы [ 4 ]

80 голосов
/ 26 сентября 2011

Строки теперь необходимы для внутреннего использования нулевых буферов. Посмотрите на определение 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()); это также означает, что даже если терминатор инициализируется лениво, невозможно наблюдать за буфером в промежуточном состоянии.

23 голосов
/ 26 сентября 2011

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

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

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

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

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

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

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

Добавление:

У меня нет доступа к фактической опубликованной окончательной спецификации C ++ 11 , но, похоже, формулировка действительно была пропущена где-то в истории изменений спецификации: например, http://www.open -std.org / ОТК1 / SC22 / wg21 / документы / документы / 2011 / n3242.pdf

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

§ 21.4.7.1 accessors basic_string [string.accessors]

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

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

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

s = s1 + s2 + s3 + s4;

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

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

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

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

2 голосов
/ 26 сентября 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...