我应该返回一个右值引用(通过std :: move'ing)吗?

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)


fre*_*low 5

函数结果的隐式移动仅适用于自动对象.右值引用参数不表示自动对象,因此在这种情况下您必须明确请求移动.


Pub*_*bby 5

第一个利用NVRO,甚至比移动更好.没有副本比廉价的更好.

第二个不能利用NVRO.假设没有省略,return temp;将调用复制构造函数并return std::move(temp);调用移动构造函数.现在,我相信其中任何一个都有相同的潜力被淘汰,所以你应该选择更便宜的,如果没有被淘汰,那就是使用std::move.

  • 调用`std :: move`将禁止NRVO,因为return语句的表达式不再是"非易失性自动对象的名称"(不是'temp`之前就是这样的名字,但无论如何). (2认同)