Когда я должен использовать string_view в интерфейсе?

16

Я использую внутреннюю библиотеку, которая была разработана для имитации предложенной библиотеки C ++ , и иногда в последние несколько лет я вижу, что ее интерфейс изменился с использования std::stringна string_view.

Поэтому я покорно изменяю свой код, чтобы соответствовать новому интерфейсу. К сожалению, я должен передать параметр std :: string и возвращаемое значение std :: string. Итак, мой код изменился примерно так:

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   api.setup (p1, special_number_to_string(p2));
}

в

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   const std::string p2_storage(special_number_to_string(p2));
   api.setup (string_view(&p1[0], p1.size()), string_view(&p2_storage[0], p2_storage.size()));
}

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

Однако, этот подход, кажется, следует совету, который я вижу в другом месте, например, этот ответ :

Кроме того, начиная с C ++ 17, вы должны избегать передачи const std :: string & в пользу std :: string_view:

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

Так когда же следует использовать string_view, а когда нет?

ТЕД
источник
1
Вы никогда не должны вызывать std::string_viewконструктор напрямую, вы должны просто передать строки в метод, принимая std::string_viewнепосредственно, и он будет автоматически преобразован.
Mgetz
@Mgetz - Хммм. Я (пока) не использую полноценный компилятор C ++ 17, так что, возможно, это большая часть проблемы. Тем не менее, пример кода здесь, кажется, указывает на его обязательность, по крайней мере, при объявлении.
TED
4
Смотрите мой ответ, оператор конвертации находится в <string>шапке и происходит автоматически. Этот код обманчив и неправильн.
Mgetz
1
"с менее безопасным", как фрагмент менее безопасен, чем ссылка на строку?
CodesInChaos
3
@TED ​​Вызывающая сторона может так же легко освободить строку, на которую указывает ваша ссылка, и освободить память, на которую указывает фрагмент.
CodesInChaos

Ответы:

18
  1. Должен ли функционал, принимающий значение, вступать во владение строкой? Если это так, используйте std::string(non-const, non-ref). Эта опция дает вам возможность явно перемещать значение, если вы знаете, что оно никогда больше не будет использоваться в контексте вызова.
  2. Функциональность просто читает строку? Если это так, используйте std::string_view(const, non-ref), это потому, что string_viewможно обрабатывать std::stringи char*легко без проблем и без копирования. Это должно заменить все const std::string&параметры.

В конечном итоге вам никогда не нужно вызывать std::string_viewконструктор, как вы. std::stringимеет оператор преобразования, который обрабатывает преобразование автоматически.

Mgetz
источник
Просто чтобы прояснить один момент, я думаю, что такой оператор преобразования также позаботился бы о худшем из проблем жизненного цикла, убедившись, что значение строки RHS остается неизменным на протяжении всей продолжительности вызова?
TED
3
@TED, если вы просто читаете значение, оно будет больше, чем вызов. Если вы вступаете во владение, тогда это должно пережить вызов. Следовательно, почему я обратился к обоим случаям. Оператор преобразования просто делает его std::string_viewпроще в использовании. Если разработчик использует его не по назначению, это ошибка программирования. std::string_viewстрого не владеющий.
Mgetz
Почему const, non-ref? Параметр const соответствует конкретному использованию, но в общем случае считается неконстантным. А ты пропустил 3. Можно принимать ломтики
v.oddou
В чем проблема прохождения const std::string_view &вместо const std::string &?
ceztko
@ceztko это совершенно не нужно и добавляет дополнительную косвенность при доступе к данным.
Mgetz
15

A std::string_viewприносит некоторые преимущества a const char*для C ++: в отличие от std::stringstring_view

  • не владеет памятью,
  • не выделяет память,
  • может указывать на существующую строку с некоторым смещением, и
  • имеет на один уровень косвенности указателя меньше, чем a std::string&.

Это означает, что string_view может часто избегать копий, не имея дело с необработанными указателями.

В современном коде std::string_viewследует заменить практически все виды использования const std::string&параметров функции. Это должно быть совместимым с исходным кодом изменением, поскольку std::stringобъявляет оператор преобразования в std::string_view.

То, что представление строки не помогает в вашем конкретном случае использования, когда вам все равно нужно создавать строку, не означает, что это плохая идея в целом. Стандартная библиотека C ++ имеет тенденцию быть оптимизированной для универсальности, а не для удобства. «Менее безопасный» аргумент не выполняется, так как нет необходимости создавать строковое представление самостоятельно.

Амон
источник
2
Большим недостатком std::string_viewявляется отсутствие c_str()метода, что приводит к ненужным промежуточным std::stringобъектам, которые необходимо создавать и размещать. Это особенно проблема в низкоуровневых API.
Матиас
1
@Matthias Это хороший момент, но я не думаю, что это огромный недостаток. Строковое представление позволяет вам указать на существующую строку с некоторым смещением. Эта подстрока не может заканчиваться нулем, для этого вам нужна копия. Строковый просмотр не запрещает вам делать копии. Это позволяет выполнять много задач по обработке строк, которые можно выполнять с помощью итераторов. Но вы правы в том, что API, которым нужна строка C, не будет извлекать выгоду из представлений. Ссылка на строку может быть более подходящей.
Amon
@Matthias, не совпадает ли string_view :: data () с c_str ()?
Элиан
3
@Jeevaka строка C должна заканчиваться нулем, но данные представления строки обычно не заканчиваются нулем, потому что она указывает на существующую строку. Например, если у нас есть строка abcdef\0и строковое представление, указывающее на cdeподстроку, после нулевой eстроки после - нет исходной строки f. В стандартных также отмечает: «данные () может возвращать указатель на буфер , который не является нулем. Поэтому, как правило, ошибочно передавать data () в функцию, которая принимает только const charT * и ожидает строку с нулевым символом в конце ».
amon
1
@kayleeFrye_onDeck Данные уже являются указателем на символ. Проблема со строками C заключается не в получении указателя на символ, а в том, что строка C должна заканчиваться нулем. Смотрите мой предыдущий комментарий для примера.
Амон
8

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

Я думаю, что это немного неправильное понимание цели этого. Хотя это и есть «оптимизация», вы должны думать об этом как о том, чтобы избавиться от необходимости использовать a std::string.

Пользователи C ++ создали десятки различных классов строк. Строковые классы фиксированной длины, SSO-оптимизированные классы с размером буфера в качестве параметра шаблона, строковые классы, в которых хранится хеш-значение, используемое для их сравнения, и т. Д. Некоторые люди даже используют строки на основе COW. Если есть что-то, что любят программисты на C ++, это пишут строковые классы.

И это игнорирует строки, которые создаются и принадлежат библиотекам C. Голый char*с, может быть, с размером какой-то.

Поэтому, если вы пишете какую-то библиотеку и берете a const std::string&, пользователь теперь должен взять любую строку, которую он использовал, и скопировать ее в a std::string. Может быть, десятки раз.

Если вы хотите получить доступ к std::stringспецифическому для строки интерфейсу, зачем вам копировать строку? Это такая трата.

Принципиальные причины не принимать string_viewв качестве параметра:

  1. Если ваша конечная цель - передать строку в интерфейс, который принимает строку, оканчивающуюся NUL ( fopenи т. Д.). std::stringгарантированно прекращается NUL; string_viewнет. И очень просто вставить представление, чтобы оно не заканчивалось NUL; Подстрока a std::stringкопирует подстроку в NUL-концевой диапазон.

    Я написал специальный тип стиля string_view с завершением NUL для именно этого сценария. Вы можете выполнять большинство операций, но не те, которые нарушают его NUL-завершенное состояние (обрезка с конца, например).

  2. Жизненные проблемы. Если вам действительно нужно скопировать это std::stringили иным образом получить массив символов, чтобы пережить вызов функции, лучше указать это заранее, взяв a const std::string &. Или просто std::stringв качестве параметра значения. Таким образом, если у них уже есть такая строка, вы можете немедленно требовать владения ею, и вызывающая сторона может перейти в строку, если им не нужно хранить ее копию.

Николь Болас
источник
Это правда? Единственный стандартный строковый класс, о котором я знал в C ++ до этого, был std :: string. Существует некоторая поддержка использования char * в качестве «строк» ​​для обратной совместимости с C, но мне почти никогда не нужно это использовать. Конечно, существует множество пользовательских классов сторонних разработчиков для почти всего, что вы можете себе представить, и, вероятно, сюда включены строки, но мне почти никогда не приходится их использовать.
TED
@TED: То, что вы «почти никогда не должны их использовать», не означает, что другие люди обычно не используют их. string_viewэто тип lingua franca, который может работать с чем угодно.
Николь Болас
3
@TED: Вот почему я сказал «C ++ как среда программирования», а не «C ++ как язык / библиотека».
Николь Болас
2
@TED: « Значит, я мог бы сказать:« C ++, как среда программирования, имеет тысячи классов контейнеров »? » И это так. Но я могу написать алгоритмы, которые работают с итераторами, и любые контейнерные классы, которые следуют этой парадигме, будут работать с ними. Напротив, «алгоритмы», которые могут принимать любой непрерывный массив символов, было гораздо сложнее написать. С string_viewэтим легко.
Николь Болас
1
@TED: Массивы символов - это особый случай. Они чрезвычайно распространены, и разные контейнеры смежных символов отличаются только тем, как они управляют своей памятью, а не тем, как вы выполняете итерации по данным. Поэтому имеет смысл иметь единый тип диапазона lingua franca, который может охватывать все такие случаи без использования шаблона. Обобщение за пределами этого - область Range TS и шаблоны.
Николь Болас