首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++ 物件複製控制深度解析:複製建構子、賦值運算子與解構子

     編輯:關於C++

在 C++ 物件導向程式設計中,「物件的生命週期管理」是核心議題。複製控制包含三大支柱:複製建構子、賦值運算子 與 解構子。它們決定了物件如何誕生、如何轉移資源、以及如何體面地離場。本文將系統剖析這三者的機制、編譯器合成規則、淺/深複製陷阱,以及經典的「三法則」。

一、複製建構子

定義與簽名

複製建構子同樣是建構子,函數名與類別名相同、無回傳值。其關鍵特徵在於唯一參數必須是「本類別型別的引用」(通常加 const 修飾):

class MyClass {
public:
    MyClass(const MyClass& other); // 標準複製建構子簽名
};

為何參數必須是引用?

若參數為傳值(MyClass other),呼叫時需建立引數副本,這又會觸發複製建構子,導致無限遞迴,最終堆疊溢位。引用機制打破了這個死循環。

觸發時機(五大場景)

  1. 以同類物件顯式/隱式初始化另一物件:MyClass a = b;
  2. 物件傳值給函數參數時:void func(MyClass obj);
  3. 函數傳值回傳物件時:MyClass func() { MyClass a; return a; }
  4. 初始化順序容器元素時:std::vector<MyClass> v(10, obj);
  5. 以初始化列表初始化陣列元素時:MyClass arr[3] = {obj1, obj2, obj3};

初始化 vs 賦值

新手易混淆兩者:

  • 初始化:物件剛形成時賦予值(呼叫複製建構子)。MyClass a = b;
  • 賦值:物件已存在,重新設定值(呼叫賦值運算子)。MyClass a; a = b;

編譯器合成規則:Trivial vs Non-trivial

並非所有類別都會獲得編譯器合成的複製建構子。只有符合 Non-trivial 條件時,編譯器才會真正合成程式碼。四大觸發條件:

  1. 含有成員物件,且該成員類別有複製建構子。
  2. 基底類別有複製建構子。
  3. 類別存在虛函數(需設定 vptr)。
  4. 繼承鏈中存在虛基底類別(需維護位置資訊)。

若以上條件皆無,複製屬於 Trivial,編譯器僅做位元複製,不產生函數呼叫開銷。

淺複製與深複製

預設合成行為為淺複製(逐成員複製)。若類別含原始指標,僅複製指標值,導致雙重釋放或記憶體洩漏。此時必須自訂複製建構子實作深複製(配置新記憶體並複製指向內容)。

二、賦值運算子

何時需自訂?

當「逐成員賦值」無法滿足需求時,典型案例即為類別含指標成員。需釋放舊資源、配置新資源、複製內容。

編譯器合成時機(四大條件)

與複製建構子極度相似:

  1. 成員物件類別有賦值運算子。
  2. 基底類別有賦值運算子。
  3. 類別宣告虛函數(不能直接複製 vptr,因右值可能為派生類別物件)。
  4. 繼承虛基底類別(虛基底部分位置可能改變)。

反組證實驗:簡單類別 class A { int a; }; 執行 a2 = a1; 時,反組譯碼無函數呼叫,純指令搬移;加入虛函數後,出現合成賦值運算子呼叫。

自訂實作關鍵點

  • 必須為成員函數(非 friend)。
  • 回傳 *this 參考以支援鏈式賦值。
  • 自我賦值檢查:if (this == &rhs) return *this;,防釋放自身資源導致災難。
  • 步驟:釋放舊資源 → 配置新資源 → 複製資料 → 回傳 *this。

複製建構子與賦值運算子的協同

兩者操作語意相近,時機不同。實務鐵律:若需要其中一個,幾乎一定也需要另一個。建議將兩者視為一個單元共同維護。

三、解構子

角色與簽名

與建構子對應:無回傳值、無參數、名稱前加 ~。負責釋放建構子取得的資源(記憶體、檔案控制代碼、鎖等)。

自動呼叫時機

  • 區域物件離開作用域。
  • delete 動態配置物件的指標時。
  • 容器/陣列銷毀時,依序銷毀元素。

關鍵誤區:指標/參考本身離開作用域不會觸發所指物件解構子。唯有 delete ptr 或實體物件離開作用域才會。

合成解構子行為

  • 按成員宣告順序逆序呼叫成員解構子。
  • 呼叫基底類別解構子(逆序)。
  • 不會 delete 指標成員指向的物件(這正是記憶體洩漏常見來源)。

編譯器合成條件

  1. 成員物件有解構子(合成或自訂)。
  2. 基底類別有解構子。

單純的虛函數/虛繼承不會強制合成解構子(對編譯器而言只是指標,無需額外操作),這從反組譯碼可驗證。

解構執行順序總覽

  1. 執行解構子函數本體。
  2. 成員物件解構子(宣告逆序)。
  3. 重設 vptr 指向基底類別虛表(若有虛函數)。
  4. 直接非虛基底類別解構子(宣告逆序)。
  5. 虛基底類別解構子(最尾端類別負責,構造逆序)。

經驗法則:三法則

若類別需要自訂解構子,則幾乎必定也需要自訂複製建構子與賦值運算子。

這是資源管理類別(RAII)設計的基石。現代 C++ (C++11 起) 更引入移動語意,擴展為「五法則」(加上移動建構子與移動賦值運算子),但核心邏輯不變:誰擁有資源,誰負責複製/移動/銷毀的語意。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved