正如@JDługosz 在评论中指出的那样,Herb 在另一个(稍后?)谈话中给出了其他建议,大致从这里看到:https ://youtu.be/xnqTKD8uD64?t=54m50s 。
他的建议归结为仅对采用f
所谓的接收器参数的函数使用值参数,假设您将从这些接收器参数中移动构造。
f
与分别针对左值和右值参数定制的最佳实现相比,这种通用方法仅增加了左值和右值参数的移动构造函数的开销。要了解为什么会出现这种情况,假设f
需要一个值参数,其中T
是一些复制和移动可构造类型:
void f(T x) {
T y{std::move(x)};
}
使用左值参数调用f
将导致调用构造函数的复制构造函数x
和调用构造函数的移动构造函数y
。另一方面,f
使用右值参数调用会导致调用构造函数x
移动构造函数,调用构造函数调用另一个移动构造函数y
。
一般来说,f
左值参数的最佳实现如下:
void f(const T& x) {
T y{x};
}
在这种情况下,只调用一个复制构造函数来构造y
。右值参数的最佳实现f
通常如下:
void f(T&& x) {
T y{std::move(x)};
}
在这种情况下,只调用一个移动构造函数来构造y
。
因此,一个明智的折衷方案是采用一个值参数,并针对最佳实现对左值或右值参数进行一个额外的移动构造函数调用,这也是 Herb 演讲中给出的建议。
正如@JDługosz 在评论中指出的那样,按值传递仅对将从 sink 参数构造某个对象的函数有意义。当您有一个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% 正确的话,会导致代码更加复杂(有关血淋淋的细节,请参阅谈话)。