如评论中@JDługosz指出的那样,Herb在另一场(晚一些的?)演讲中给出了其他建议,大约从这里开始看:
https://youtu.be/xnqTKD8uD64?t=54m50s。
他的建议是:只为带有所谓“sink参数”的函数
f
使用值参数,假设您将从这些sink参数中进行移动构造。
与针对lvalue和rvalue参数分别量身定制的
f
的最佳实现相比,这种通用方法仅增加了一个移动构造函数的开销。要了解为什么会这样,请假设
f
采用值参数,其中
T
是某个可复制和可移动构造类型:
void f(T x) {
T y{std::move(x)};
}
使用lvalue参数调用f
将导致调用复制构造函数来构造x
,并调用移动构造函数来构造y
。另一方面,使用rvalue参数调用f
将导致调用移动构造函数来构造x
,并再次调用移动构造函数来构造y
。
通常,针对lvalue参数的f
的最佳实现如下:
void f(const T& x) {
T y{x};
}
在这种情况下,只调用一个复制构造函数来构造
y
。对于右值参数,
f
的最佳实现通常如下:
void f(T&& x) {
T y{std::move(x)};
}
在这种情况下,只调用一个移动构造函数来构造
y
。
因此,一个明智的折衷方案是采用值参数,并针对与最优实现相比的lvalue或rvalue参数进行一次额外的移动构造函数调用,这也是Herb在演讲中给出的建议。
正如@JDługosz在评论中指出的那样,仅通过值传递对于将从sink参数构建某个对象的函数才有意义。当您拥有一个复制其参数的函数
f
时,与通用的传递-const引用方法相比,传递-值方法将具有更多的开销。对于保留其参数副本的函数
f
,传递值的方法将具有以下形式:
void f(T x) {
T y{...};
...
y = std::move(x);
}
在这种情况下,对于左值参数,存在一个复制构造和移动赋值,对于右值参数,则存在一个移动构造和移动赋值。最优秀的左值参数情况为:
void f(const T& x) {
T y{...};
...
y = x;
}
这仅涉及一个赋值操作,与通过值传递所需的复制构造函数和移动赋值相比,成本可能要低得多。原因是赋值操作可能会重用y
中已经分配的内存,从而避免(解)分配内存,而复制构造函数通常会分配内存。
对于rvalue参数,保留副本的最优实现的形式为:f
:
void f(T&& x) {
T y{...};
...
y = std::move(x);
}
因此,在这种情况下只需要移动分配。将一个rvalue传递给接受const引用的f
版本只需要进行赋值而不是移动分配。因此,相对而言,在这种情况下,采用接受const引用的f
版本作为通用实现更为可取。
因此,为了获得最优实现,通常需要进行重载或某种完美转发,正如演讲中所示。缺点是需要过载参数数量与f
的参数数目相关的组合爆炸方式。完美转发的缺点是f
将成为模板函数,这将阻止它成为虚函数,并且如果想要100%正确的结果,会导致显著更复杂的代码(请参见演讲的详细信息)。
std::string_view
,就再也没有一个好的理由传递字符串的引用了。但是考虑到你提出这个问题的时间,这可能不是你想要的答案! - undefined