6

Покончены ли дни передачи const std::string & в качестве параметра?

1

Я слышал недавний доклад Герба Саттера, который утверждал, что причины передавать std::vector и std::string по const & в значительной мере утратили свою актуальность. Он предложил, что написание функции вроде следующей сейчас предпочтительнее:

std::string do_something ( std::string inval )
{
   std::string return_val;
   // ... выполняем действия ...
   return return_val;
}

Я понимаю, что return_val будет rvalue в момент возврата из функции и, следовательно, может быть возвращен с использованием семантики перемещения, что очень эффективно. Однако inval все еще значительно больше, чем размер ссылки (которая обычно реализуется как указатель). Это связано с тем, что std::string имеет различные компоненты, включая указатель на кучу и член char[] для оптимизации коротких строк. Поэтому мне кажется, что передача по ссылке все еще является хорошей идеей.

Может кто-то объяснить, почему Герб мог это сказать?

5 ответ(ов)

1

Нет. Многие люди воспринимают этот совет (включая Дейва Абрахамса) слишком широко, применяя его ко всем параметрам типа std::string, что не является "лучшей практикой" для всех случаев. Передача std::string по значению не всегда оптимальна и не подходит для любых произвольных ситуаций, так как оптимизации, о которых говорится в подобных обсуждениях и статьях, применимы только к ограниченному набору случаев.

Если вы возвращаете значение, изменяете параметр или берете значение, то передача по значению может сэкономить дорогостоящие копирования и предложить синтаксическое удобство.

Тем не менее, передача по константной ссылке экономит много копий, когда копия не требуется.

Теперь к конкретному примеру:

Однако inval все же значительно больше, чем размер ссылки (которая обычно реализуется как указатель). Это связано с тем, что std::string включает в себя различные компоненты, такие как указатель в куче и массив char[] для оптимизации коротких строк. Поэтому мне кажется, что передача по ссылке все еще является хорошей идеей. Может кто-то объяснить, почему Герб мог это сказать?

Если размер стека является проблемой (и предполагая, что это не встроенная/оптимизированная функция), return_val + inval > return_val — другими словами, пик использования стека может быть уменьшен при передаче по значению (примечание: это упрощение ABI). Тем временем, передача по константной ссылке может отключить оптимизации. Основная причина здесь не в том, чтобы избежать роста стека, а в том, чтобы гарантировать, что оптимизация может быть выполнена где это применимо.

Эпоха передачи по константной ссылке не закончилась — просто правила стали более сложными, чем когда-либо. Если производительность важна, вам будет разумно учитывать, как вы передаете такие типы, исходя из деталей, которые вы используете в своих реализациях.

0

Это во многом зависит от реализации компилятора.

Однако это также зависит от того, что вы используете.

Рассмотрим следующие функции:

bool foo1( const std::string v )
{
  return v.empty();
}
bool foo2( const std::string & v )
{
  return v.empty();
}

Эти функции реализованы в отдельном модуле компиляции, чтобы избежать инлайнинга. Тогда:

  1. Если вы передадите литерал в обе эти функции, вы не увидите значительной разницы в производительности. В обоих случаях необходимо создать объект строки.

  2. Если вы передадите другой объект std::string, foo2 будет быстрее, так как foo1 выполнит глубокое копирование.

На моем ПК, используя g++ 4.6.1, я получил такие результаты:

  • передача переменной по ссылке: 1,000,000,000 итераций → время выполнения: 2.25912 секунды
  • передача переменной по значению: 1,000,000,000 итераций → время выполнения: 27.2259 секунды
  • передача литерала по ссылке: 100,000,000 итераций → время выполнения: 9.10319 секунды
  • передача литерала по значению: 100,000,000 итераций → время выполнения: 8.62659 секунды
0

Если вам действительно не нужна копия, то разумно использовать const &. Например:

bool isprint(std::string const &s) {
    return all_of(begin(s), end(s), (bool(*)(char))isprint);
}

Если вы измените этот код, чтобы принимать строку по значению, вам придется переместить или скопировать параметр, и в этом нет необходимости. Кроме того, операции копирования и перемещения могут быть более затратными по времени, а также создают новые потенциальные ситуации с ошибками; копирование или перемещение могут выбросить исключение (например, не удалось выделить память во время копирования), в то время как ссылка на существующее значение не может этого сделать.

Если вам действительно нужна копия, тогда передача и возврат по значению обычно (всегда?) являются лучшим вариантом. На самом деле, в C++03 я бы не переживал об этом, если только дополнительные копии не вызывают проблем с производительностью. Устранение копий (copy elision) кажется довольно надежным на современных компиляторах. Я думаю, что скептицизм людей и их настойчивость в необходимости проверять поддержку RVO в таблицах компиляторов в значительной степени устарели сейчас.


Вкратце, C++11 не меняет ничего в этом отношении, кроме случаев, когда люди не доверяли устранению копий.

0

Почти.

В C++17 у нас есть basic_string_view<?>, что сводит применение параметров std::string const& к очень узкому кейсу.

Существование семантики перемещения устранило один из случаев использования std::string const&: если вы планируете сохранить параметр, передача std::string по значению будет более оптимальной, так как вы можете move из параметра.

Если кто-то вызовет вашу функцию с сырым C-строкой "string", то будет выделен только один буфер std::string, в отличие от случая с std::string const&, где выделяется два буфера.

Тем не менее, если вы не собираетесь делать копию, использование std::string const& по-прежнему полезно в C++14.

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

Таким образом, использование std::string const& актуально тогда, когда вы не копируете данные полностью и собираетесь передать их в C-стиль API, который ожидает нуль-терминализованный буфер, и вам нужны более высокоуровневые строковые функции, которые предоставляет std::string. На практике это довольно редкий набор требований.

0

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

Таким образом, ответ таков: это зависит от обстоятельств:

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