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

После C++ 11 я подумал о c_str() а также data() эквивалентно

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

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

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

const CharT* c_str() const;

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

4 ответа

Решение

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

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

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

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

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

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

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

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

Аннотация
Является std::stringотсутствие неконстантности .data() функция-член надзор или намеренный дизайн, основанный на pre-C++11 std::string семантика? В любом случае это отсутствие функциональности побуждает разработчиков использовать небезопасные альтернативы в нескольких законных сценариях. В этой статье говорится о добавлении неконстантного .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 Функция-член также удобна при вызове функции библиотеки C, которая не имеет квалификации const для своих параметров строки C. Это распространено в старых кодах и тех, которые должны быть переносимы со старыми компиляторами Си.

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

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

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

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

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

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

Другие вопросы по тегам