這是 MTK 筆試總結系列的第二篇,重點探討 C++ 中極具影響力的編譯器優化技術——返回值優化(Return Value Optimization, RVO)及其具名變體 NRVO。透過一道經典的筆試題,我們將剖析編譯器如何消除不必要的拷貝構造與賦值操作,顯著提升程式效能。
題目使用一個名為 X 的類別,該類別包含建構函式、拷貝建構函式、解構函式、前置/後置遞增運算子、賦值運算子、輸出運算子及加法運算子。所有函式均會輸出追蹤訊息,方便觀察物件生命週期。
class X {
public:
X() { cout << "default construct" << endl; }
X(int a) : i(a) { cout << "construct " << i << endl; }
~X() { cout << "desconstruct " << i << endl; }
X(const X& x) : i(x.i) { cout << "copy construct " << i << endl; }
X& operator++() { /* 前置遞增 */ }
const X operator++(int) { /* 後置遞增 */ }
X& operator=(int m) { /* int 賦值 */ }
X& operator=(const X& x) { /* 物件賦值 */ }
friend ostream& operator<<(ostream& os, const X& x) { os << x.i; return os; }
friend X operator+(const X& a, const X& b) {
cout << "operator +" << endl;
return X(a.i + b.i);
}
public:
int i;
};
X c = a + b;執行碼:
X a(10), b(20);
X c = a + b;
輸出結果(GCC 4.5 / MSVC 9.0 Debug & Release 一致):
construct 10
construct 20
operator +
construct 30
desconstruct 30
desconstruct 20
desconstruct 10
分析: 整個過程未出現 "copy construct" 或 "operator="。編譯器直接在 c 的記憶體空間建構 operator+ 的返回值。這就是返回值優化(RVO)——編譯器將臨時物件的建構直接安置於目標物件位置,省略拷貝與銷毀。
X c; c = a + b;執行碼:
X a(10), b(20), c;
c = a + b;
輸出結果:
construct 10
construct 20
default construct // c 預設建構
operator +
construct 30 // 臨時物件建構
operator =(X) // 呼叫賦值運算子
desconstruct 30 // 臨時物件解構
desconstruct 30 // c 解構
desconstruct 20
desconstruct 10
差異原因: c 在賦值前已存在(已預設建構),編譯器無法直接覆寫其記憶體,必須產生臨時物件再呼叫 operator=。這多出了 default construct、operator=(X)、desconstruct 30 三行。
return X(...);),直接在呼叫端的儲存空間建構。X temp; return temp;),同樣消除拷貝。MSVC 9.0 僅在 Release 模式預設開啟 NRVO。本例中 operator+ 回傳無名臨時物件 X(a.i+b.i),屬於 RVO 範疇。現代編譯器(C++17 起)甚至將 RVO 列為強制優化(強制省略拷貝)。
X c = a + b;),讓編譯器發揮 RVO/NRVO 最大效益。掌握 RVO/NRVO 不僅是面試加分項,更是寫出現代高效 C++ 代碼的基石。