我有一个基础课,让我们说MyVector<T>
。我希望下面的表达式尽可能地懒惰(尽可能有效):
MyVector<int> x = a + b + c;
+
的semantichs将连接向量,因此[1,2,3] + [2,3,4]
必须得[1,2,3,2,3,4]
。假设我有operator+=
,我可以在任何需要的地方依赖它。我认为以下实现对于评估a+b
的情况就足够了:
template<typename T>
MyVector<T> operator+(MyVector<T> const &lhs, MyVector<T> const &rhs)
{
return MyVector<T>(lhs)+=rhs;
}
然后我将得到一个右值(a+b
的结果)和一个左值(c
)。所以我想我必须写一些类似的东西:
template<typename T>
MyVector<T> ??? operator+(MyVector<T> &&lhs, MyVector<T> const &rhs)
{
???;
}
这里应该返回什么类型以及如何组装?
答案 0 :(得分:4)
你绝对应该返回一个对象,而不是一个引用(否则连接rvalues可以返回一个悬空引用)。
如果你希望串联在所有情况下都是最优的,你需要遵循std::string
的例子,它为rvalue和lvalue的每个组合提供了四个重载:
X operator+(const X& l, const X& r) { X x(l); x += r; return x;}
X operator+(const X& l, X&& r) { r.insert(begin(), l.begin(), l.end()); return r; }
X operator+(X&& l, const X& r) { l += r; return l; }
X operator+(X&& l, X&& r) { l += r; return l; }
当(至少)其中一个是rvalue时,这将始终重用其中一个参数中的现有内存。
执行return lhs += rhs;
时应该小心,因为operator+=
的结果可能是左值引用,它不符合复制省略(RVO)或移入返回值的条件。执行lhs += rhs; return lhs;
或return std::move(lhs += rhs);
以确保您不会在return语句中强制执行不必要的副本。
您可以通过查看哪个列表更长并附加到该列表来进一步优化最后一个重载(例如,如果l
为空并且r
非常大l += r
将复制每个元素何时可以返回r
)
X operator+(X&& l, X&& r)
{
if (l.size() > r.size())
{
l += r;
return l;
}
r.insert(begin(), l.begin(), l.end());
return r;
}
答案 1 :(得分:2)
如果你的类(足够快)可移动,你可以给出一个实现,它按值获取第一个参数:
template<typename T>
MyVector<T> operator+(MyVector<T> lhs, MyVector<T> const &rhs)
{
return std::move(lhs += rhs);
}
将通过复制或从调用点处的调用者参数移动来透明地为您创建新的MyVector<T>
。这种方法的缺点是复制/移动是在函数调用之外完成的,这可能会对异常和调试产生意外。
答案 2 :(得分:0)
您只需要返回MyVector<T>
即可。如果您的类具有移动构造函数或移动赋值运算符,则编译器将调用它,从而允许您更有效地复制/分配对象。
至于程序集,这将取决于底层实现。 lhs += rhs
是一个合理的默认值,但您也可以选择通过引用传递参数,然后分配预先分配长度为lhs.size() + rhs.size()
的向量的结果。