在诸如Operator Overloading之类的问题+答案中,指出重载二元运算符(例如operator+
)的最佳方法是:
class X {
X& operator+=(const X& rhs)
{
// actual addition of rhs to *this
return *this;
}
};
inline X operator+(X lhs, const X& rhs)
{
lhs += rhs;
return lhs;
}
operator+
本身按lhs
按值rhs
按const引用,并按值返回已更改的lhs
。
如果将rvalue作为lhs
调用,我无法理解这里会发生什么:这仍然是需要的单一定义(并且编译器会优化参数的移动和返回值),或者添加第二个重载版本的运算符与rvalue引用一起使用是否有意义?
修改
有趣的是,在Boost.Operators中,他们讨论了这种实现:
T operator+( const T& lhs, const T& rhs )
{
T nrv( lhs );
nrv += rhs;
return nrv;
}
允许命名返回值优化,但默认情况下不使用它,因为:
可悲的是,并非所有编译器都实现了NRVO,有些甚至以不正确的方式实现它,这使得它在这里无用
这些新信息不足以让我提供完整的答案,但它可能会让其他一些聪明的头脑得出一个包罗万象的结论。
答案 0 :(得分:4)
此签名:
inline X operator+(X lhs, const X& rhs)
允许rvalues和lvalues作为操作的左侧。左值将被复制到lhs
,xvalues将被移入lhs
,而prvalues将被直接初始化为lhs
。
当我们链接多个lhs
操作时,将lhs
除以{和const&
除+
之间的区别。我们来吧:
+====================+==============+=================+
| | X const& lhs | X lhs |
+--------------------+--------------+-----------------+
| X sum = a+b; | 1 copy | 1 copy, 1 move |
| X sum = X{}+b; | 1 copy | 1 move |
| X sum = a+b+c; | 2 copies | 1 copy, 2 moves |
| X sum = X{}+b+c; | 2 copies | 2 moves |
| X sum = a+b+c+d; | 3 copies | 1 copy, 3 moves |
| X sum = X{}+b+c+d; | 3 copies | 3 moves |
+====================+==============+=================+
按const&
的第一个参数缩放份数。每个操作都是一个副本。通过值的第一个参数按比例缩放。每个操作只需一次移动但,第一个参数是左值意味着额外的副本(或xvalues的附加移动)。
如果您的类型移动成本不高 - 对于移动和复制相同的情况 - 那么您希望通过const&
获取第一个参数,因为它至少与其他情况一样好而且没有理由大惊小怪。
但如果移动成本更低,你实际上可能希望两个重载:
X operator+(X const& lhs, X const& rhs) {
X tmp(lhs);
tmp += rhs;
return tmp;
}
X operator+(X&& lhs, X const& rhs) {
lhs += rhs;
return std::move(lhs);
}
这将为所有中间临时对象使用移动而不是副本,但会在第一个上保存一次移动。不幸的是,最好的解决方案也是最冗长的。
答案 1 :(得分:0)
如果使用右值引用作为第一个参数调用运算符,例如当使用std :: move或函数调用的直接结果时,将调用lhs的移动构造函数。不需要额外的重载,它需要rvalue引用。