Как отмечает @ JDługosz в комментариях, Херб дает другой совет в другом (позже?) Выступлении, смотрите примерно здесь: https://youtu.be/xnqTKD8uD64?t=54m50s .
Его совет сводится только к использованию параметров-значений для функции, fкоторая принимает так называемые аргументы-приемники, при условии, что вы будете перемещать конструкцию из этих аргументов-приемников.
Этот общий подход добавляет только издержки конструктора перемещения для аргументов lvalue и rvalue по сравнению с оптимальной реализацией fспециально настроенных аргументов lvalue и rvalue соответственно. Чтобы понять, почему это так, предположим, что fпринимает параметр-значение, где Tесть некоторый тип копирования и перемещения:
void f(T x) {
T y{std::move(x)};
}
Вызов fс аргументом lvalue приведет к тому, что конструктор копирования вызывается для конструирования x, а конструктор перемещения вызывается для конструирования y. С другой стороны, вызов fс аргументом rvalue вызовет конструктор перемещения для вызова и вызовет конструктор xдругого перемещения y.
В общем, оптимальная реализация fаргументов для lvalue выглядит следующим образом:
void f(const T& x) {
T y{x};
}
В этом случае для конструирования вызывается только один конструктор копирования y. Оптимальная реализация fаргументов для rvalue, опять же, в общем, выглядит следующим образом:
void f(T&& x) {
T y{std::move(x)};
}
В этом случае для конструирования вызывается только один конструктор перемещения y.
Таким образом, разумный компромисс состоит в том, чтобы принять параметр-значение и иметь один дополнительный вызов конструктора перемещения для аргументов lvalue или rvalue относительно оптимальной реализации, что также является советом, данным в выступлении Херба.
Как отметил @ JDługosz в комментариях, передача по значению имеет смысл только для функций, которые будут создавать некоторый объект из аргумента приемника. Когда у вас есть функция, fкоторая копирует свой аргумент, подход с передачей по значению будет иметь больше издержек, чем общий подход с передачей по константной ссылке. Подход с передачей по значению для функции, fкоторая сохраняет копию своего параметра, будет иметь вид:
void f(T x) {
T y{...};
...
y = std::move(x);
}
В этом случае есть конструкция копирования и назначение перемещения для аргумента lvalue, а также конструкция перемещения и назначение перемещения для аргумента rvalue. Наиболее оптимальный случай для аргумента lvalue:
void f(const T& x) {
T y{...};
...
y = x;
}
Это сводится только к назначению, которое потенциально намного дешевле, чем конструктор копирования, плюс назначение перемещения, необходимое для подхода с передачей по значению. Причина этого заключается в том, что назначение может повторно использовать существующую выделенную память вy и, следовательно, предотвращать (де) выделение, тогда как конструктор копирования обычно выделяет память.
Для аргумента rvalue наиболее оптимальная реализация, в fкоторой сохраняется копия, имеет вид:
void f(T&& x) {
T y{...};
...
y = std::move(x);
}
Таким образом, в этом случае только назначение перемещения. Передача r-значения в версию, fкоторая принимает константную ссылку, стоит только назначение, а не перемещение. Итак, условно говоря, версияf использования константной ссылки в этом случае в качестве общей реализации является предпочтительным.
Таким образом, в общем, для наиболее оптимальной реализации вам потребуется перегрузить или выполнить какую-то идеальную пересылку, как показано в докладе. Недостатком является комбинаторный рост количества требуемых перегрузок, в зависимости от количества параметров, на fслучай, если вы решите перегрузить категорию значения аргумента. У идеальной пересылки есть недостаток, который fпревращается в функцию шаблона, которая не позволяет сделать его виртуальным, и приводит к значительно более сложному коду, если вы хотите сделать его на 100% правильным (подробности см. В докладе).