標籤:

什麼時候應當依靠返回值優化(RVO)?

C++的函數返回vector之類或者自定義類型時會產生額外的拷貝構造函數、析構函數開銷,例如

class A{
......
};
A foo(){
......
}
int main(){
......
A c = foo();
......
}

foo()函數中的局部變數會被用來進行拷貝構造函數然後析構,以前學C++時一直說的是要避免直接返回大對象,寧可用引用或指針傳進來。但是返回值優化有時可以把這些額外的拷貝構造等開銷節省下來,所以:

  1. 是不是現在的C++函數在返回自定義類型或者vector等時不必再關心效率問題?
  2. 似乎返回值優化也有失效的時候,具體是在什麼情況下呢?
  3. C++11有了右值引用和移動語義,函數可以用移動語義來返回嗎,如果可以,什麼時候用移動語義,什麼什麼依賴編譯器的RVO呢?


根據effective modern c++中介紹,編譯器進行RVO條件有二

  1. return 的值類型與 函數簽名的返回值類型相同
  2. return的是一個局部對象

現在我們來考慮下面這個語句

return std::move(w)

此時返回的並不是一個局部對象,而是局部對象的右值引用。編譯器此時無法進行rvo優化,能做的只有根據std::move(w)來移動構造一個臨時對象,然後再將該臨時對象賦值到最後的目標。所以,不要試圖去返回一個局部對象的右值引用。

下面來談一下右值引用與函數之間的關係。

第一個例子:

std::vector& return_vector(void)
{
std::vector& tmp {1,2,3,4,5};
return tmp;
}
std::vector& rval_ref = return_vector();

此時,並不調用RVO,拷貝構造臨時對象,同時臨時對象的生命周期延長至與rval_ref相同,等價於下面的代碼

const std::vector& rval_ref = return_vector();

第二個例子:

std::vector& return_vector(void)
{
std::vector& tmp {1,2,3,4,5};
return std::move(tmp);
}

std::vector& rval_ref = return_vector();

該代碼會造成一個運行時錯誤,因為rval_ref最終指向被析構了的tmp 。類似於返回了內部對象的左值引用。

第三個例子:

std::vector& return_vector(void)
{
std::vector& tmp {1,2,3,4,5};
return std::move(tmp);
}
std::vector& rval_ref = return_vector();

該例子類似於第一個例子,只不過臨時對象的構造是由右值移動構造的。

最好的例子:

std::vector& return_vector(void)
{
std::vector& tmp {1,2,3,4,5};
return tmp;
}
std::vector& rval_ref = return_vector();

該代碼會調用RVO,不生成臨時對象 ,返樸歸真了。

參考鏈接(其實完全翻譯自此鏈接):c++ - C++11 rvalues and move semantics confusion (return statement)


推薦閱讀:

認真學完 C++ Primer 後,C++ 語言到了什麼水平?
為什麼C++編譯器不能發現未初始化的變數?
數據結構課本中的「生命遊戲」有哪些奇葩的玩法?
如何理解《Effective C++》第31條將文件間的編譯依賴關係降低的方法?
輪子哥的C++為什麼學的這麼好?請分享一下學習C++秘訣?

TAG:C | CC | 編譯器 |