在 C++ 物件導向程式設計中,「物件的生命週期管理」是核心議題。複製控制包含三大支柱:複製建構子、賦值運算子 與 解構子。它們決定了物件如何誕生、如何轉移資源、以及如何體面地離場。本文將系統剖析這三者的機制、編譯器合成規則、淺/深複製陷阱,以及經典的「三法則」。
複製建構子同樣是建構子,函數名與類別名相同、無回傳值。其關鍵特徵在於唯一參數必須是「本類別型別的引用」(通常加 const 修飾):
class MyClass {
public:
MyClass(const MyClass& other); // 標準複製建構子簽名
};
若參數為傳值(MyClass other),呼叫時需建立引數副本,這又會觸發複製建構子,導致無限遞迴,最終堆疊溢位。引用機制打破了這個死循環。
MyClass a = b;void func(MyClass obj);MyClass func() { MyClass a; return a; }std::vector<MyClass> v(10, obj);MyClass arr[3] = {obj1, obj2, obj3};新手易混淆兩者:
MyClass a = b;MyClass a; a = b;並非所有類別都會獲得編譯器合成的複製建構子。只有符合 Non-trivial 條件時,編譯器才會真正合成程式碼。四大觸發條件:
若以上條件皆無,複製屬於 Trivial,編譯器僅做位元複製,不產生函數呼叫開銷。
預設合成行為為淺複製(逐成員複製)。若類別含原始指標,僅複製指標值,導致雙重釋放或記憶體洩漏。此時必須自訂複製建構子實作深複製(配置新記憶體並複製指向內容)。
當「逐成員賦值」無法滿足需求時,典型案例即為類別含指標成員。需釋放舊資源、配置新資源、複製內容。
與複製建構子極度相似:
反組證實驗:簡單類別 class A { int a; }; 執行 a2 = a1; 時,反組譯碼無函數呼叫,純指令搬移;加入虛函數後,出現合成賦值運算子呼叫。
*this 參考以支援鏈式賦值。if (this == &rhs) return *this;,防釋放自身資源導致災難。*this。兩者操作語意相近,時機不同。實務鐵律:若需要其中一個,幾乎一定也需要另一個。建議將兩者視為一個單元共同維護。
與建構子對應:無回傳值、無參數、名稱前加 ~。負責釋放建構子取得的資源(記憶體、檔案控制代碼、鎖等)。
delete 動態配置物件的指標時。關鍵誤區:指標/參考本身離開作用域不會觸發所指物件解構子。唯有 delete ptr 或實體物件離開作用域才會。
單純的虛函數/虛繼承不會強制合成解構子(對編譯器而言只是指標,無需額外操作),這從反組譯碼可驗證。
若類別需要自訂解構子,則幾乎必定也需要自訂複製建構子與賦值運算子。
這是資源管理類別(RAII)設計的基石。現代 C++ (C++11 起) 更引入移動語意,擴展為「五法則」(加上移動建構子與移動賦值運算子),但核心邏輯不變:誰擁有資源,誰負責複製/移動/銷毀的語意。