2012-11-17 30 views
16

一个C++Next blog post说,回用右值引用

A compute(…) 
{ 
    A v; 
    … 
    return v; 
} 

如果A具有可访问的复制或移动构造函数,编译器可能选择的Elid到副本。否则,如果A有一个移动构造函数,则v被移动。否则,如果A具有复制构造函数,则复制v。 否则,会发出编译时错误。

我以为我应该总是返回的值没有std::move ,因为编译器将能够找出用户的最佳选择。但是从博客另一个例子发布

Matrix operator+(Matrix&& temp, Matrix&& y) 
    { temp += y; return std::move(temp); } 

这里std::move是必要的,因为y必须作为函数内的左值来处理。

啊,在学习这篇博客文章后,我的头几乎炸开了。我尽力去理解推理,但我研究得越多,我就变得越困惑。为什么我们应该在std::move的帮助下返回价值?

+1

你有一个链接到有问题的文章? –

+1

[相关常见问题](http://stackoverflow.com/questions/3106110/) – fredoverflow

+1

http://cpp-next.com/archive/2009/09/making-your-next-move/,这是一个系列右值引用 – StereoMatching

回答

21

所以,让我们说你有:

A compute() 
{ 
    A v; 
    … 
    return v; 
} 

而你正在做的:

A a = compute(); 

有两种传输(复制或移动)中涉及的这个表达。首先,函数中由v表示的对象必须被转移到该函数的结果,即由compute()表达式贡献的值。我们称之为Transfer 1.然后,这个临时对象被转移来创建a表示的对象 - 传输2.

在很多情况下,编译器可以忽略Transfer 1和2 - 直接构造对象va的位置,并且不需要转移。在这个例子中,编译器必须使用Named Return Value Optimization for Transfer 1,因为返回的对象是被命名的。但是,如果我们禁用复制/移动省略,则每次传输都会调用A的拷贝构造函数或其移动构造函数。在大多数现代编译器中,编译器将看到v即将被销毁,它将首先将其移入返回值。然后这个临时返回值将被移至a。如果A没有移动构造函数,它将被复制用于两次传输。

现在让我们看一下:

A compute(A&& v) 
{ 
    return v; 
} 

我们返回的值来自参考被传递给函数。编译器不只是假设v是一个临时的,它可以从它移动。在这种情况下,Transfer 1将成为副本。然后转移2将是一个举动 - 没关系,因为返回的值仍然是一个临时的(我们没有返回一个引用)。但由于我们知道,我们已经采取了对象,我们可以从移动,因为我们的参数是一个右值引用,我们可以明确地告诉编译器把v作为临时与std::move

A compute(A&& v) 
{ 
    return std::move(v); 
} 

现在转移1和转移2将会移动。


之所以编译器不会自动把v,定义为A&&,作为右值是安全的一个。弄清楚这不仅仅是太愚蠢。一旦一个对象有一个名字,它可以在整个代码中被多次引用。试想一下:

A compute(A&& a) 
{ 
    doSomething(a); 
    doSomethingElse(a); 
} 

如果a作为右值是自动处理,doSomething可以自由地撕裂它的胆量了,这意味着a传递给doSomethingElse可能无效。即使doSomething通过值取其参数,该对象也将从下一行中移出,因此无效。为了避免这个问题,指定的右值引用是左值。这意味着当doSomething被调用时,a最坏的情况下将被复制,如果不是仅由左值引用 - 它将在下一行仍然有效。

这是由作者compute说,“好吧,现在我允许这个值被移出,因为我确定它是一个临时对象”。你可以这么说std::move(a)。例如,你可以给doSomething副本,然后让doSomethingElse从中移动:

A compute(A&& a) 
{ 
    doSomething(a); 
    doSomethingElse(std::move(a)); 
} 
+0

你的意思是编译器可以隐式地移动或者隐去自动对象吗?所以在第二种情况下,我们应该使用std :: move来帮助编译器明白&& v是一个右值,因为我们的编译器不够聪明,不知道我们不知道不再需要v了吗? – StereoMatching

+0

嗯,真相是一个有名的右值引用是一个左值表达式。表达式'v'是一个左值,即使它的类型是一个右值引用。并不是编译器不够聪明 - 将它视为右值会很危险。我会在我的答案中加一点解释原因。 –

+0

谢谢,我觉得我知道什么是右值参考“再次”。这件事情非常复杂,我不知道这个功能只会被那些库开发人员和编译器厂商使用。 – StereoMatching

5

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

+0

关键是,第一个例子中的'v'在函数结束时会被销毁,所以它的最后一个操作是一个移动而不是一个副本。然而在第二个例子中,由'temp'引用的对象将会在函数的末尾生存。 –

4

第一个利用NVRO比移动更好。 没有副本比一个便宜。

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

+2

调用'std :: move'将禁止NRVO,因为return语句的表达式不再是“非易失性自动对象的名称”(而不是'temp'之前是这样一个名称,但不管怎么说)。 – Xeo