2012-11-17 27 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