正如@JDługosz在评论中指出的那样,Herb在另一个(后来?)演讲中提出了其他建议,请大致从此处查看:https ://youtu.be/xnqTKD8uD64?t=54m50s 。
他的建议可以归结为仅对函数使用值参数 f
接收器参数,假定您将构造从这些接收器参数中移出。
f
与分别针对左值和右值参数量身定制的最佳实现相比,这种通用方法仅增加了左值和右值参数的移动构造函数的开销。为了了解为什么会发生这种情况,假设f
有一个value参数,其中T
有一些复制并移动可构造类型:
void f(T x) {
T y{std::move(x)};
}
f
用左值参数调用将导致复制构造函数被调用来构造x
,而移动构造函数被调用来构造y
。另一方面,f
使用rvalue参数调用将导致调用move构造函数进行构造x
,而另一个move构造函数来构造y
。
通常,f
for左值参数的最佳实现如下:
void f(const T& x) {
T y{x};
}
在这种情况下,仅调用一个副本构造函数来构造y
。通常,f
for rvalue参数的最佳实现如下:
void f(T&& x) {
T y{std::move(x)};
}
在这种情况下,只有一个move构造函数被调用来构造y
。
因此,明智的折衷办法是采用一个值参数,并针对最佳实现对lvalue或rvalue参数进行一个额外的move构造函数调用,这也是Herb演讲中给出的建议。
正如@JDługosz在评论中指出的那样,按值传递仅对将从接收器参数构造某些对象的函数有意义。当您有一个f
复制其参数的函数时,按值传递方法将比一般的按常量引用引用方法具有更多的开销。f
保留其参数副本的函数的按值传递方法将具有以下形式:
void f(T x) {
T y{...};
...
y = std::move(x);
}
在这种情况下,有一个复制构造和一个左值参数的移动分配,以及一个复制结构和一个右值参数的移动分配。左值参数的最佳情况是:
void f(const T& x) {
T y{...};
...
y = x;
}
归结为仅是一个赋值,它可能比按值传递方法所需的复制构造函数和移动赋值便宜得多。这样做的原因是,该分配可能会重复使用中现有的已分配内存y
,从而防止(取消)分配,而复制构造函数通常会分配内存。
对于右值参数f
,保留副本的最佳实现形式为:
void f(T&& x) {
T y{...};
...
y = std::move(x);
}
因此,在这种情况下,只有一个移动分配。将右值传递给f
采用const引用的版本时,只需花费一个分配,而不是移动分配。因此相对而言,f
在这种情况下采用const引用作为一般实现的版本是可取的。
因此,总的来说,对于最佳的实现,您将需要重载或进行某种完美的转发,如演讲中所示。缺点是需要重载的数量激增,具体取决于参数的数量f
,以防万一您选择对参数的值类别重载。完美转发的缺点是f
变成了模板功能,从而阻止了它的虚拟化,并且如果您想100%正确地使用它,则会导致代码复杂得多(有关详细信息,请参见讨论)。