Совет по умолчанию для использования строковых литералов C-стиля или построения неназванных объектов std::string?

Итак, С++ 14 ввел ряд пользовательских литералов для использования, один из которых - "s" буквальный суффикс, для создания std::string объектов. Согласно документации, ее поведение точно такое же, как и при создании объекта std::string, например:

auto str = "Hello World!"s; // RHS is equivalent to: std::string{ "Hello World!" }

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

Итак, мой вопрос прост: В каких случаях хорошая (или плохая) идея создает неназванный объект std::string вместо простого использования строкового литерала в стиле C?


Пример 1:

Рассмотрим следующее:

void foo(std::string arg);

foo("bar");  // option 1
foo("bar"s); // option 2

Если я прав, первый метод вызовет соответствующую конструкторскую перегрузку std::string, чтобы создать объект внутри области foo, а второй метод сначала построит неназванный строковый объект, а затем move-construct foo. Хотя я уверен, что компиляторы очень хорошо оптимизируют такие вещи, но, тем не менее, вторая версия кажется, что она включает дополнительный ход, в отличие от первой альтернативы (не так, как движение, конечно, дорого). Но опять же, после компиляции с разумным компилятором, конечные результаты, скорее всего, будут сильно оптимизированы и в любом случае свободны от дублирования и перемещений/копий.

Кроме того, что, если foo перегружен, чтобы принимать ссылки rvalue? В этом случае, я думаю, было бы разумно называть foo("bar"s), но я мог ошибаться.


Пример 2:

Рассмотрим следующее:

std::cout << "Hello World!" << std::endl;  // option 1
std::cout << "Hello World!"s << std::endl; // option 2

В этом случае объект std::string, вероятно, передается оператору cout с помощью ссылки rvalue, и первый вариант, вероятно, имеет указатель, поэтому обе операции очень дешевы, а вторая имеет дополнительные затраты на построение объект сначала. Вероятно, это более безопасный способ (?).


Во всех случаях, конечно, создание объекта std::string может привести к распределению кучи, которое может быть выбрано, поэтому безопасность исключений также должна приниматься во внимание. Это больше проблема во втором примере, хотя, как и в первом примере, объект std::string будет построен в обоих случаях в любом случае. На практике получение исключения из построения строкового объекта очень маловероятно, но в некоторых случаях может быть допустимым аргументом.

Если вы можете придумать больше примеров для рассмотрения, пожалуйста, включите их в свой ответ. Меня интересует общий совет относительно использования неназванных объектов std::string, а не только этих двух частных случаев. Я только включил их, чтобы указать на некоторые из моих мыслей относительно этой темы.

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

Ответ 1

В каких случаях это хорошая (или плохая) идея построить неназванный std::string объект, вместо простого использования строкового литерала в стиле C?

A std::string - литерал - хорошая идея, когда вы специально хотите переменную типа std::string, будь то для

  • изменение значения позже (auto s = "123"s; s += '\n';)

  • более богатый, интуитивно понятный и менее подверженный ошибкам интерфейс (семантика значений, итераторы, find, size и т.д.)

    • Значение семантика означает ==, < копирование и т.д., работая над значениями, в отличие от семантики указателя/по-ссылке после того, как литералы C-строк распадаются на const char* s
  • вызов some_templated_function("123"s) будет сжато гарантировать создание <std::string>, при этом аргумент может обрабатываться с использованием семантики значений внутри

    • если вы знаете, что любой код, создающий экземпляр шаблона для std::string, в любом случае, и он имеет значительную сложность по сравнению с вашими ограничениями ресурсов, вам может понадобиться передать std::string, чтобы избежать излишней инстанцирования для const char* тоже, но это редко нужно заботиться
  • значения, содержащие внедренные NUL s

Строковый литерал в стиле C может быть предпочтительным, где:

  • Требуется семантика стиля указателя (или, по крайней мере, не проблема)

  • значение будет передано только функциям, ожидающим const char*, или std::string temporaries будут построены в любом случае, и вам все равно, что вы предоставляете вашему оптимизатору компилятора дополнительное препятствие для перехода на достичь возможности создания компиляции или времени загрузки, если существует возможность повторного использования одного и того же экземпляра std::string (например, при передаче функций с помощью const -reference) - снова это редко нужно заботиться.

  • (еще один редкий и неприятный взлом), вы каким-то образом используете поведение пула строк в компиляторе, например. если он гарантирует, что для любой единицы перевода строки const char* в строковые литералы будут (но, конечно же, всегда) отличаться, если текст отличается

    • вы не можете получить то же самое от std::string .data()/.c_str(), так как один и тот же адрес может быть связан с другим текстом (и разными экземплярами std::string) во время выполнения программы и std::string буферы на разных адресах могут содержать один и тот же текст
  • вам будет полезно, если указатель останется в силе после того, как std::string покинет область действия и будет уничтожен (например, данный enum My_Enum { Zero, One }; - const char* str(My_Enum e) { return e == Zero ? "0" : "1"; } безопасен, но const char* str(My_Enum e) { return e == Zero ? "0"s.c_str() : "1"s.c_str(); } is not and std::string str(My_Enum e) { return e == Zero ? "0"s : "1"s; } smacks преждевременного пессимизма при использовании динамического распределения (без SSO или для более длинного текста))

  • вы используете объединение времени компиляции смежных литералов в C-строках (например, "abc" "xyz" становится одним смежным const char[] literal "abcxyz")), что особенно полезно в макроподстановках

  • вы ограничены памятью и/или не хотите подвергать риску исключение или сбой при распределении динамической памяти

Обсуждение

[basic.string.literals] 21.7:

string operator "" s(const char* str, size_t len);

Возвращает: string{str,len}

В принципе, использование ""s вызывает функцию, которая возвращает значение std::string по значению - в решающей степени вы можете привязать ссылку const или ссылку на rvalue, но не ссылку lvalue.

Когда используется для вызова void foo(std::string arg);, arg будет действительно двигаться.

Кроме того, что, если foo перегружен, чтобы принимать ссылки rvalue? В этом случае, я думаю, имеет смысл называть foo ( "bar" ), но я мог ошибаться.

Неважно, что вы выбираете. Техническое обслуживание - если foo(const std::string&) когда-либо изменено на foo(const char*), только призывы foo("xyz"); будут беспрерывно продолжать работать, но есть очень мало смутно правдоподобных причин, по которым это может быть (так что код C мог бы назвать это тоже?), d быть немного сумасшедшим, чтобы не продолжать перегружать foo(const std::string&) для существующего клиентского кода, поэтому он может быть реализован в C? - возможно, удаление зависимости от заголовка <string>? - не имеет отношения к современным вычислительным ресурсам).

std:: cout < "Привет мир!" & Л; < станд:: епсИ;//опция 1

std:: cout < "Hello World!" S < станд:: епсИ;//опция 2

Первый вызовет operator<<(std::ostream&, const char*), напрямую обращаясь к данным строкового литерала с единственным недостатком, заключающимся в том, что потоковая передача может потребоваться для сканирования завершающего NUL. "Вариант 2" будет соответствовать перегрузке const -reference и предполагает создание временного, хотя компиляторы могут оптимизировать его, чтобы они не делали это без необходимости часто или даже эффективно создавали строковый объект во время компиляции может быть применимо только для строк, достаточно коротких, чтобы использовать подход Short String Optimization (SSO) в объекте. Если они уже не делают таких оптимизаций, потенциальная выгода и, следовательно, давление/желание сделать это, вероятно, увеличится.

Ответ 2

Сначала я считаю, что ответ основан на мнениях!

В вашем примере 1 вы уже упоминали все важные аргументы для использования нового литерала s. И да, я ожидаю, что результат будет таким же, поэтому я не вижу необходимости говорить, что я хочу std::string в определении.

Один аргумент может состоять в том, что конструктор определен explicit, и автоматического преобразования типов не произойдет. При этом условии полезен литерал s.

Но это вопрос вкуса, я думаю!

В вашем примере 2 я обычно использую "старую" версию c-строки, потому что генерация объекта std::string имеет накладные расходы. Дать указатель на строку для cout хорошо определен, и я не вижу случая, когда я могу иметь какую-то выгоду.

Итак, мой личный совет на самом деле (каждый день доступна новая информация:-)), чтобы использовать c-строку, если это точно соответствует моим потребностям. Это означает: строка является постоянной и никогда не будет скопирована или изменена и будет использоваться только как "как есть". Таким образом, std::string просто не принесет пользы.

И использование 's'-literal используется, когда мне нужно определить, что это std::string.

Вкратце: я не использую std::string, если мне не нужны дополнительные функции, которые std::string предлагает по старой c-строке. Для меня дело не в использовании s-literal, а в использовании std::string против c-строк вообще.

Только как замечание: мне приходится много программировать на очень маленьких встроенных устройствах, особенно на 8-битных AVR. Использование std::string приводит к большим накладным расходам. Если мне нужно использовать динамический контейнер, потому что мне нужны функции этого контейнера, очень хорошо иметь тот, который очень хорошо реализован и протестирован. Но если мне не нужно, это просто дорого использовать.

На большой цели, такой как поле x86, кажется, что она не имеет значения для std::string вместо c-string. Но наличие небольшого устройства в уме дает вам представление о том, что на самом деле происходит и на больших машинах.

Только мои два цента!

Ответ 3

В каких случаях хорошая (или плохая) идея создает неназванный объект std::string вместо простого использования строкового литерала в стиле C?

То, что есть или не хорошая идея, имеет тенденцию меняться в зависимости от ситуации.

Мой выбор заключается в использовании необработанных литералов, когда они достаточно (всякий раз, когда мне не нужно ничего, кроме литерала). Если мне нужно получить доступ к чему-либо другому, кроме указателя на первый элемент для строки (длина строки, ее обратно, итераторы или что-то еще), тогда я использую литерал std::string.

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

Uhh..., в то время как код действительно может выкинуть, это не имеет значения, если только в особых обстоятельствах (например, встроенный код, работающий на - или близком к - пределы памяти оборудования или приложения/среды с высокой доступностью).

На практике у меня никогда не было проблем с памятью, от написания auto a = "abdce"s; или другого подобного кода.

В заключение не беспокойтесь о безопасности исключений из-за нехватки памяти, возникающих при создании экземпляра std::string. Если вы столкнулись с ситуацией с нехваткой памяти, измените код, когда найдете ошибку.