C++ 按值返回函數會產生臨時對象,導致額外建構/拷貝/解構開銷。返回值優化 (RVO) 與具名返回值優化 (NRVO) 是編譯器消除這些臨時對象的關鍵技術。本文透過 Complex 類別實例,詳細追蹤編譯器轉換過程、臨時對象產生原因、RVO/NRVO 條件與寫法建議。
class Complex {
double real, imag;
public:
Complex(double r=0, double i=0) : real(r), imag(i) { cout << "建構"; }
Complex(const Complex& c) : real(c.real), imag(c.imag) { cout << "拷貝建構"; }
Complex& operator=(const Complex& c) { real=c.real; imag=c.imag; cout << "指派"; return *this; }
~Complex() { cout << "解構"; }
};
Complex operator+(const Complex& a, const Complex& b) {
Complex retVal(a.real+b.real, a.imag+b.imag);
return retVal; // 可能產生臨時對象
}
Complex a(1,1), b(2,2), c;
c = a + b;
編譯器將按值返回轉換為「輸出參數」模式:
// 原始
Complex operator+(const Complex& a, const Complex& b);
// 編譯器轉換為
void Complex_Add(Complex& __tempResult, const Complex& a, const Complex& b);
呼叫端 c = a + b 展開為:
Complex __tempResult;
Complex_Add(__tempResult, a, b);
c = __tempResult; // 指派操作
Complex retVal(...) → 1 次建構__tempResult.Complex::Complex(retVal) → 1 次拷貝建構retVal.~Complex() → 1 次解構c.operator=(__tempResult) → 1 次指派__tempResult.~Complex() → 1 次解構總計:3 建構 + 1 拷貝建構 + 1 指派 + 4 解構 = 嚴重開銷
void Complex_Add(Complex& __tempResult, const Complex& a, const Complex& b) {
__tempResult.Complex::Complex(); // 直接在 __tempResult 建構
__tempResult.real = a.real + b.real;
__tempResult.imag = a.imag + b.imag;
// 無 retVal,無拷貝,無額外解構
}
效果:消除函數內部臨時對象 retVal 及其拷貝/解構。
// 具名變數 (NRVO) - 視編譯器支援度
Complex operator+(const Complex& a, const Complex& b) {
Complex retVal(a.real+b.real, a.imag+b.imag);
return retVal;
}
// 未命名臨時對象 (RVO) - 較通用支援
Complex operator+(const Complex& a, const Complex& b) {
return Complex(a.real+b.real, a.imag+b.imag); // 直接建構於呼叫端空間
}
現代編譯器 (GCC, Clang, MSVC) 對兩種形式支援良好,但未命名臨時對象形式最保險。
__tempResult即使函數內部 RVO 成功,呼叫端 c = a + b 仍產生 __tempResult 並觸發指派。
c 已存在 (預設建構),指派運算子要求右值來源,故需臨時對象。
// 指派版本 (產生臨時對象)
Complex a(1,1), b(2,2), c;
c = a + b;
// 直接初始化版本 (無臨時對象,C++17 強制 RVO)
Complex a(1,1), b(2,2);
Complex c = a + b; // 或 Complex c(a + b);
| 寫法 | 建構 | 拷貝建構 | 指派 | 解構 |
|---|---|---|---|---|
c = a + b | 4 | 1 | 1 | 4 |
Complex c = a + b | 3 | 0 | 0 | 3 |
C++17 標準化 RVO/NRVO 為強制行為 (非優化):
T x = T(args); 直接在 x 建構,無臨時對象return expr; 其中 expr 為純右值,直接在呼叫端空間建構cout),也必須省略return Type(args); 未命名臨時對象形式Type x = func(); 而非 x = func();-fno-elide-constructors 關閉優化對比RVO/NRVO 是 C++ 編譯器最重要的優化之一,消除按值返回的臨時對象開銷。理解其觸發條件、寫法配合與 C++17 強制複製省略,能寫出兼具表達力與高效能的 C++ 代碼。