R. *_*des 15 c++ rvalue-reference c++11
考虑以下:
struct vec
{
int v[3];
vec() : v() {};
vec(int x, int y, int z) : v{x,y,z} {};
vec(const vec& that) = default;
vec& operator=(const vec& that) = default;
~vec() = default;
vec& operator+=(const vec& that)
{
v[0] += that.v[0];
v[1] += that.v[1];
v[2] += that.v[2];
return *this;
}
};
vec operator+(const vec& lhs, const vec& rhs)
{
return vec(lhs.v[0] + rhs.v[0], lhs.v[1] + rhs.v[1], lhs.v[2] + rhs.v[2]);
}
vec&& operator+(vec&& lhs, const vec& rhs)
{
return move(lhs += rhs);
}
vec&& operator+(const vec& lhs, vec&& rhs)
{
return move(rhs += lhs);
}
vec&& operator+(vec&& lhs, vec&& rhs)
{
return move(lhs += rhs);
}
Run Code Online (Sandbox Code Playgroud)
感谢r值引用,运算符+这四个重载可以通过重用临时值来最小化创建的对象数量.但我不喜欢这个引入的代码重复.我可以用更少的重复来实现同样的目标吗?
sel*_*tze 32
回收临时值是一个有趣的想法,你并不是唯一一个编写函数来返回rvalue引用的人.在旧的C++ 0x草案操作符+(字符串&&,字符串const&)也被声明为返回右值引用.但这有很好的理由改变了.我看到了这种重载和返回类型选择的三个问题.其中两个独立于实际类型,第三个参数指的是那种类型vec.
安全问题.考虑这样的代码:
vec a = ....;
vec b = ....;
vec c = ....;
auto&& x = a+b+c;
Run Code Online (Sandbox Code Playgroud)
如果您的最后一个运算符返回右值引用,x则将是一个悬空引用.否则,它不会.这不是一个人为的例子.例如,该auto&&技巧在内部使用for-range循环以避免不必要的副本.但是,由于参考绑定期间临时对象的生命周期扩展规则不适用于仅返回引用的函数调用,因此您将获得悬空引用.
string source1();
string source2();
string source3();
....
int main() {
for ( char x : source1()+source2()+source3() ) {}
}
Run Code Online (Sandbox Code Playgroud)
如果最后一个运算符+返回对第一次串联期间创建的临时值的rvalue引用,则此代码将调用未定义的行为,因为字符串临时不会存在足够长的时间.
在通用代码中,返回rvalue引用的函数会强制您编写
typename std::decay<decltype(a+b+c)>::type
Run Code Online (Sandbox Code Playgroud)
代替
decltype(a+b+c)
Run Code Online (Sandbox Code Playgroud)
只是因为最后一个op +可能会返回一个右值引用.在我的拙见中,这变得越来越难看.
由于您的类型vec既"平坦"又小,这些op +重载几乎没用.请参阅FredOverflow的回答.
结论:应避免使用具有右值引用返回类型的函数,尤其是当这些引用可能引用短期临时对象时.std::move并且std::forward是这个经验法则的特殊用途例外.
由于您的vec类型是"平坦的"(没有外部数据),因此移动和复制完全相同.所以你所有的右值参考都会让std::move你绝对没有任何表现.
我将摆脱所有额外的重载,并编写经典的引用到const版本:
vec operator+(const vec& lhs, const vec& rhs)
{
return vec(lhs.v[0] + rhs.v[0], lhs.v[1] + rhs.v[1], lhs.v[2] + rhs.v[2]);
}
Run Code Online (Sandbox Code Playgroud)
如果您对移动语义知之甚少,我建议研究这个问题.
感谢r值引用,运算符+这四个重载可以通过重用临时值来最小化创建的对象数量.
除了少数例外,返回rvalue引用是一个非常糟糕的主意,因为这些函数的调用是xvalues而不是prvalues,并且你可能会遇到令人讨厌的临时对象生存期问题.不要这样做.
这在当前的C++中已经非常有效,它将在C++ 0x中使用移动语义(如果可用).它已经处理了所有情况,但依赖于复制省略和内联来避免复制 - 因此它可能会产生比所需更多的副本,特别是对于第二个参数.关于这个的好处是它没有任何其他重载工作,并做正确的事情(语义):
vec operator+(vec a, vec const &b) {
a += b;
return a; // "a" is local, so this is implicitly "return std::move(a)",
// if move semantics are available for the type.
}
Run Code Online (Sandbox Code Playgroud)
这是99%的时间你会停下来的地方.(我可能低估了这个数字.)这个答案的其余部分只适用于你知道,例如通过使用分析器,op +的额外副本值得进一步优化.
要完全避免所有可能的副本/移动,您确实需要这些重载:
// lvalue + lvalue
vec operator+(vec const &a, vec const &b) {
vec x (a);
x += b;
return x;
}
// rvalue + lvalue
vec&& operator+(vec &&a, vec const &b) {
a += b;
return std::move(a);
}
// lvalue + rvalue
vec&& operator+(vec const &a, vec &&b) {
b += a;
return std::move(b);
}
// rvalue + rvalue, needed to disambiguate above two
vec&& operator+(vec &&a, vec &&b) {
a += b;
return std::move(a);
}
Run Code Online (Sandbox Code Playgroud)
你和你的一起走在正确的轨道上,没有真正的减少(AFAICT),但是如果你经常需要这种op +用于许多类型,宏或CRTP可以为你生成它.唯一真正的区别(我对上面单独的语句的偏好是次要的)是你在operator +中添加两个左值时的副本(const vec&lhs,vec && rhs):
return std::move(rhs + lhs);
Run Code Online (Sandbox Code Playgroud)
template<class T>
struct Addable {
friend T operator+(T const &a, T const &b) {
T x (a);
x += b;
return x;
}
friend T&& operator+(T &&a, T const &b) {
a += b;
return std::move(a);
}
friend T&& operator+(T const &a, T &&b) {
b += a;
return std::move(b);
}
friend T&& operator+(T &&a, T &&b) {
a += b;
return std::move(a);
}
};
struct vec : Addable<vec> {
//...
vec& operator+=(vec const &x);
};
Run Code Online (Sandbox Code Playgroud)
现在不再需要为vec定义任何op +.对于任何具有op + =的类型,Addable都是可重用的.
| 归档时间: |
|
| 查看次数: |
4757 次 |
| 最近记录: |