C++ 函數以值回傳區域物件時,歷經 C++98 複製、C++11 移動、C++17 強制複製省略 (Guaranteed Copy Elision) 三個時代。本文實測三種情境:**預設拷貝建構子**、**`explicit` 拷貝建構子**、**`unique_ptr` 移動建構子**,揭示編譯器實際行為與開發者可控制的手段。
class Thing {
public:
int member_;
Thing() : member_(0) {}
Thing(Thing& other) : member_(other.member_) { cout << "copy\n"; } // 拷貝建構子
Thing(Thing&& other) : member_(other.member_) { cout << "move\n"; } // 移動建構子
};
Thing getThingByReturnValue() {
Thing thing;
thing.member_ = 1;
cout << "in method addr: " << &thing.member_ << endl;
return thing; // 回傳區域物件
}
Thing th = getThingByReturnValue();
cout << "out method addr: " << &th.member_ << endl;
輸出:
in method addr: 003CF798 copy out method addr: 003CF88C
**位址不同** → 發生拷貝 (或移動),編譯器選擇拷貝建構子。若啟用優化 (Release/O2),多數編譯器執行 **NRVO (Named Return Value Optimization)** 直接在呼叫端堆疊框架建構物件,**完全省略拷貝/移動**,輸出位址相同且無 "copy"/"move" 列印。
explicit Thing(Thing& other) : member_(other.member_) { cout << "copy\n"; }
輸出 (優化關閉時)**:
in method addr: 003EFC18 move out method addr: 003EFD0C
**關鍵機制**:`explicit` 拷貝建構子**不可隱性轉換**,編譯器無法以隱性拷貝初始化回傳值,**被迫選擇移動建構子** (若存在)。位址仍不同,但語意轉為移動。
unique_ptr getBySmartPtr() {
unique_ptr p(new int);
return p; // unique_ptr 刪除複製,只有移動
}
auto p = getBySmartPtr();
輸出**:
in method addr: -842150451 out method addr: -842150451
**位址相同** → **移動建構子實際僅轉移指標值**,未搬移記憶體。且編譯器對 `unique_ptr` 等標準庫型別極度優化,常直接 NRVO。
| 標準 | 規則 | 實際行為 |
|---|---|---|
| C++98 | 允許 NRVO (匿名/具名) | 編譯器自行決定,多數優化 |
| C++11 | 引入移動語義,NRVO 失效時退而求其次用移動 | 需型別有移動建構子 |
| C++17 | 強制複製省略 (prvalue 直接初始化結果物件) | **不再要求型別可複製/可移動**,零開銷保證 |