Ste*_*ing 17 c++ return rvalue-reference stdmove
一篇C++ Next博客文章说
A compute(…)
{
A v;
…
return v;
}
Run Code Online (Sandbox Code Playgroud)
如果A
具有可访问的副本或移动构造函数,则编译器可以选择忽略该副本.否则,如果A
有移动构造函数,v
则移动.否则,如果A
有复制构造函数,v
则复制.否则,将发出编译时错误.
我以为我应该总是返回值,std::move
因为编译器能够找出用户的最佳选择.但另一个例子来自博客文章
Matrix operator+(Matrix&& temp, Matrix&& y)
{ temp += y; return std::move(temp); }
Run Code Online (Sandbox Code Playgroud)
这std::move
是必要的,因为y
必须将其视为函数内的左值.
啊,在研究这篇博文之后,我的脑袋几乎爆炸了.我尽力去理解推理,但是我学的越多,我就越困惑.我们为什么要借助于这个价值来回报这个价值std::move
?
Jos*_*eld 24
所以,让我们说你有:
A compute()
{
A v;
…
return v;
}
Run Code Online (Sandbox Code Playgroud)
而且你正在做:
A a = compute();
Run Code Online (Sandbox Code Playgroud)
此表达式涉及两种传输(复制或移动).首先,函数中表示的对象v
必须转移到函数的结果,即compute()
表达式捐赠的值.让我们称之为转移1.然后,转移此临时对象以创建由a
- 转移2 表示的对象.
在许多情况下,转换器1和2都可以被编译器省略 - 对象v
直接在位置构造a
,不需要传输.在此示例中,编译器必须使用传输1的命名返回值优化,因为返回的对象已命名.但是,如果我们禁用复制/移动省略,则每次传输都需要调用A的复制构造函数或其移动构造函数.在大多数现代编译器中,编译器将看到v
即将被销毁,并且它将首先将其移动到返回值中.然后将此临时返回值移入a
.如果A
没有移动构造函数,则将为两次传输复制它.
现在让我们来看看:
A compute(A&& v)
{
return v;
}
Run Code Online (Sandbox Code Playgroud)
我们返回的值来自传递给函数的引用.编译器不只是假设它v
是一个临时的,并且可以从它移动1.在这种情况下,转移1将是一个副本.然后转移2将是一个移动 - 这没关系,因为返回的值仍然是临时的(我们没有返回引用).但是既然我们知道我们已经采用了一个可以移动的对象,因为我们的参数是一个右值引用,我们可以明确告诉编译器将其v
视为临时对象std::move
:
A compute(A&& v)
{
return std::move(v);
}
Run Code Online (Sandbox Code Playgroud)
现在转移1和转移2都将移动.
1编译器不自动处理v
(定义为A&&
rvalue)的原因之一是安全性.想出来并不是太愚蠢.一旦对象具有名称,就可以在整个代码中多次引用它.考虑:
A compute(A&& a)
{
doSomething(a);
doSomethingElse(a);
}
Run Code Online (Sandbox Code Playgroud)
如果a
被自动视为右值,则doSomething
可以自由地撕掉它的内容,这意味着a
传递给它doSomethingElse
可能是无效的.即使doSomething
按值获取其参数,对象也会从下一行中移出,因此无效.为了避免这个问题,命名的右值引用是左值.这意味着当doSomething
被调用时,a
最坏的情况将被复制,如果不是仅仅通过左值引用 - 它仍将在下一行中有效.
作者compute
应该说,"好吧,现在我允许移动这个值,因为我肯定知道它是一个临时对象".你这样说是的std::move(a)
.例如,您可以提供doSomething
副本,然后允许doSomethingElse
从中移动:
A compute(A&& a)
{
doSomething(a);
doSomethingElse(std::move(a));
}
Run Code Online (Sandbox Code Playgroud)
第一个利用NVRO,甚至比移动更好.没有副本比廉价的更好.
第二个不能利用NVRO.假设没有省略,return temp;
将调用复制构造函数并return std::move(temp);
调用移动构造函数.现在,我相信其中任何一个都有相同的潜力被淘汰,所以你应该选择更便宜的,如果没有被淘汰,那就是使用std::move
.