c_str() против data(), когда дело доходит до возвращаемого типа

После С++ 11 я думал о c_str() и data() одинаково.

С++ 17 вводит перегрузку для последнего, которая возвращает неконстантный указатель (ссылка, в которой я не уверен, полностью ли он обновился по сравнению с С++ 17):

const CharT* data() const;    (1)   
CharT* data();                (2)   (since C++17)

c_str() возвращает только постоянный указатель:

const CharT* c_str() const;

Почему дифференциация этих двух методов в С++ 17, особенно когда С++ 11 был тем, который сделал их однородными? Другими словами, почему только один метод получил перегрузку, а другой нет?

Ответ 1

Новая перегрузка была добавлена P0272R1 для С++ 17. Ни сама статья, ни ссылки в ней не обсуждают, почему только data получили новые перегрузки, а c_str - нет. Мы можем только спекулировать на этом этапе (если только люди, вовлеченные в дискуссию, не вмешиваются), но я хотел бы предложить следующие моменты для рассмотрения:

  • Даже простое добавление перегрузки к data нарушает некоторый код; Сохранение этого изменения было способом минимизировать негативное влияние.

  • Функция c_str до сих пор была полностью идентична data и фактически является "устаревшим" средством для взаимодействия кода, который принимает "строку C", то есть неизменный массив символов с нулевым символом в конце. Поскольку вы всегда можете заменить c_str data, нет особой причины для добавления в этот устаревший интерфейс.

Я понимаю, что самой мотивацией для P0292R1 было то, что существуют устаревшие API, которые ошибочно или по причинам C принимают только изменяемые указатели, даже если они не мутируют. Тем не менее, я полагаю, что мы не хотим добавлять в цепочку и без того массивный API, что абсолютно необходимо.

Еще один момент: с С++ 17 вам теперь разрешено писать в нулевой терминатор, если вы пишете ноль. (Раньше это был UB для записи чего-либо в нулевой терминатор.) Изменяемый c_str бы еще одну точку входа в эту конкретную тонкость, и чем меньше у нас тонкостей, тем лучше.

Ответ 2

Причина, по которой член data() получил перегрузку, объясняется в этой статье на open-std.org.

TL; DR статьи: добавлена неконстантная функция-член .data() для std::string чтобы улучшить единообразие в стандартной библиотеке и помочь разработчикам C++ написать правильный код. Это также удобно при вызове функции C-библиотеки, которая не имеет const квалификации для своих параметров C-строки.

Некоторые соответствующие отрывки из статьи:

Аннотация
Является ли отсутствие в std::string неконстантной функции-члена .data() оплошностью или намеренным дизайном, основанным на семантике std::string до C++ 11? В любом случае это отсутствие функциональности побуждает разработчиков использовать небезопасные альтернативы в нескольких допустимых сценариях. В этой статье говорится о добавлении неконстантной функции-члена .data() для std :: string, чтобы улучшить единообразие в стандартной библиотеке и помочь разработчикам C++ написать правильный код.

Случаи применения
Библиотеки C иногда включают подпрограммы, которые имеют параметры char *. Одним из примеров является параметр lpCommandLine функции CreateProcess в Windows API. Поскольку элемент data() в std::string является const, его нельзя использовать для того, чтобы заставить объекты std :: string работать с параметром lpCommandLine. Разработчики испытывают желание использовать .front() вместо этого, как в следующем примере.

std::string programName;
// ...
if( CreateProcess( NULL, &programName.front(), /* etc. */ ) ) {
  // etc.
} else {
  // handle error
}

Обратите внимание, что когда programName пусто, выражение programName.front() вызывает неопределенное поведение. Временная пустая C-строка исправляет ошибку.

std::string programName;
// ...

if( !programName.empty() ) { 
  char emptyString[] = {'\0'};    
  if( CreateProcess( NULL, programName.empty() ? emptyString : &programName.front(), /* etc. */ ) ) {
    // etc.
  } else {
    // handle error
  }
}

Если бы был неконстантный член .data(), как в случае с std::vector, правильный код был бы простым.

std::string programName;
// ...
if( !programName.empty() ) {
  char emptyString[] = {'\0'};
  if( CreateProcess( NULL, programName.data(), /* etc. */ ) ) {
    // etc.
  } else {
    // handle error
  }
}

.data() std::string функция-член .data() std::string также удобна при вызове функции C-библиотеки, которая не имеет квалификации const для своих параметров C-строки. Это распространено в старых кодах и тех, которые должны быть переносимы со старыми компиляторами Си.

Ответ 3

Это зависит только от семантики "что вы хотите с ней делать". Вообще говоря, std::string иногда используется как буферный вектор, т.е. Как замена std::vector<char>. Это часто можно увидеть в boost::asio. Другими словами, это массив символов.

c_str(): строго означает, что вы ищете строку с нулевым символом в конце. В этом смысле вам никогда не следует изменять данные, и вам никогда не понадобится строка как неконстантная.

data(): вам может понадобиться информация внутри строки как данные буфера, и даже как неконстантные. Вам может понадобиться, а может и не понадобиться изменить данные, что вы можете сделать, если это не связано с изменением длины строки.

Ответ 4

Две функции-члена c_str и данные std :: string существуют из-за истории класса std :: string.

До С++ 11 std :: string могла быть реализована как копирование при записи. Внутреннее представление не нуждалось в нулевом завершении сохраненной строки. Функция-член c_str убедилась, что возвращаемая строка завершена нулем. Функция-член data simlpy вернула указатель на сохраненную строку, которая не обязательно была завершена нулем. - Чтобы быть уверенным, что изменения строки были замечены для включения копирования при записи, обе функции должны были возвращать указатель на постоянные данные.

Все это изменилось в С++ 11, когда копирование при записи больше не было разрешено для std :: string. Так как c_str по- прежнему требовался для доставки строки с нулевым символом в конце, ноль всегда добавляется к фактической сохраненной строке. В противном случае вызову c_str может потребоваться изменить хранимые данные, чтобы завершить строку null, что сделает c_str неконстантной функцией. Поскольку данные доставляют указатель на сохраненную строку, они обычно имеют ту же реализацию, что и c_str. Обе функции все еще существуют из-за обратной совместимости.