Как отмечает @ 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% правильным (подробности см. В докладе).