現代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_unnamed
、getA_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