現代C++之如何返回一個對象?

  • 2019 年 12 月 30 日
  • 筆記

如何返回一個對象?

一個用來返回的對象,通常應當是可移動構造 / 賦值的,一般也同時是可拷貝構造 / 賦值的。如果這樣一個對象同時又可以默認構造,我們就稱其為一個半正則(semiregular)的對象。如果可能的話,我們應當盡量讓我們的類滿足半正則這個要求。

1.返回值優化(拷貝消除)

下面編譯的gcc版本是支援c++17的gcc8.3。如果使用gcc5.5等版本結果會不同。

ROV例子1:

#include <iostream>    using namespace std;    // Can copy and move  class A {  public:      A() { cout << "Create An"; }        ~A() { cout << "Destroy An"; }        A(const A &) { cout << "Copy An"; }        A(A &&) { cout << "Move An"; }  };    A getA_unnamed() {      return A();  }    int main() {      auto a = getA_unnamed();  }  

返回值優化輸出:

Create A  Destroy A  

禁用返回值優化程式碼修改(-fno-elide-constructors):

Create A  Destroy A  

修改程式碼如下NROV例子2:

A getA_named()  {    A a;    return a;  }    int main()  {    auto a = getA_named();  }  

優化結果同上!

Create A  Destroy A  

禁用後的結果:

Create A  Move A  Destroy A  Destroy A  

也就是說,返回內容被移動構造了。

示例3:

A getA_duang() {      A a1;      A a2;      if (rand() > 42) {          return a1;      }      else {          return a2;      }  }  

優化與禁用結果一樣:

Create A  Create A  Move A  Destroy A  Destroy A  Destroy A  

我們把移動構造刪除,得到示例4:

A(A&&)= delete;  

此時,前面的move就變成copy。

如果再進一步,把拷貝構造函數也刪除呢?得到示例5:

A(const A&&)= delete;  A(A&&)= delete;  

是不是上面的 getA_unnamedgetA_named和 getA_duang 都不能工作了?

在 C++14 及之前確實是這樣的。但從 C++17 開始,對於類似於 getA_unnamed這樣的情況,即使對象不可拷貝、不可移動,這個對象仍然是可以被返回的!C++17 要求對於這種情況,對象必須被直接構造在目標位置上,不經過任何拷貝或移動的步驟。

2.總結

  • copy construct本身在RVO和NRVO兩種情況下被優化了,如果再加上move反而畫蛇添足。

在 C++11 之前,返回一個本地對象意味著這個對象會被拷貝,除非編譯器發現可以做返回值優化(named return value optimization,或 NRVO),能把對象直接構造到調用者的棧上。從 C++11 開始,返回值優化仍可以發生,但在沒有返回值優化的情況下,編譯器將試圖把本地對象移動出去,而不是拷貝出去。這一行為不需要程式設計師手工用 std::move 進行干預——使用std::move 對於移動行為沒有幫助,反而會影響返回值優化。」

  • 加入了move assignment後,默認是調用move assignment而不是copy assignment