多重繼承基礎:一個派生類,多個直接基類
class Base1 { /* ... */ };
class Base2 { /* ... */ };
class Derived : public Base1, public Base2 { /* ... */ };
- 派生類物件包含**所有基類子物件**,記憶體佈局依宣告順序排列。
- 構造函數調用順序:**基類按宣告順序** → 成員變數按宣告順序 → 派生類構造函數體。
- 解構順序**完全相反**。
菱形繼承問題:重複基類子物件
class Vehicle { int weight; };
class Car : public Vehicle { /* ... */ };
class Boat : public Vehicle { /* ... */ };
class Amphibian : public Car, public Boat { /* ... */ };
記憶體佈局 (無虛擬繼承)
Amphibian 物件:
+------------------+
| Vehicle (via Car) | weight = 4
+------------------+
| Car 成員 |
+------------------+
| Vehicle (via Boat) | weight = 4 ← 重複!
+------------------+
| Boat 成員 |
+------------------+
| Amphibian 成員 |
+------------------+
帶來的三大問題
- 資料冗余:
Vehicle::weight 存在兩份,浪費記憶體、狀態不一致風險。
- 存取模糊:
amphibian.weight / amphibian.SetWeight() 編譯錯誤「請求的成員不明確」。
- 構造混亂:
Amphibian 構造函數必須顯式呼叫 Vehicle 構造函數,否則兩個基類子物件各自預設構造。
虛擬繼承:共享單一基類子物件
class Car : virtual public Vehicle { /* ... */ };
class Boat : virtual public Vehicle { /* ... */ };
class Amphibian : public Car, public Boat { /* ... */ };
記憶體佈局 (虛擬繼承)
Amphibian 物件:
+------------------+
| Vehicle (共享) | weight = 4 ← 單一份
+------------------+
| Car 成員 | (含 vbptr 指向虛基類表)
+------------------+
| Boat 成員 | (含 vbptr)
+------------------+
| Amphibian 成員 |
+------------------+
核心機制:虛基類表 (vbtable) + 虛基類指標 (vbptr)
- 每個**虛繼承的中間類別** (Car, Boat) 含 **vbptr**,指向該類別的 **vbtable**。
- vbtable 記錄 **虛基類相對於該類別的偏移量**。
- 存取虛基類成員時:
this->vbptr->vbtable[Vehicle] 計算偏移 → 定位共享 Vehicle 子物件。
- **最派生類** (Amphibian) **負責構造虛基類**,中間類別構造函數**不再**呼叫虛基類構造函數(否則重複構造)。
構造函數調用順序 (虛擬繼承)
class Vehicle { Vehicle(int w) { cout << "Vehicle(" << w << ")"; } };
class Car : virtual public Vehicle { Car(int w) : Vehicle(w) { cout << "Car"; } };
class Boat : virtual public Vehicle { Boat(int w) : Vehicle(w) { cout << "Boat"; } };
class Amphibian : public Car, public Boat {
Amphibian(int w) : Vehicle(w), Car(w), Boat(w) { cout << "Amphibian"; }
};
// 輸出順序:
// Vehicle(4) → Car → Boat → Amphibian
// 注意:Vehicle 只構造一次,由最派生類 Amphibian 主動呼叫
關鍵規則
- 虛基類由**最派生類**直接構造,**中間類別構造函數忽略**虛基類初始化列表。
- 若最派生類**未顯式呼叫**虛基類構造函數,則呼叫虛基類**預設構造函數** (必須存在)。
- 構造順序:**虛基類** (按深度優先、左到右) → **非虛基類** (宣告順序) → 成員 → 派生類體。
虛擬繼承 vs 虛函數:完全無關的兩個概念
| 特性 | 虛擬繼承 | 虛函數 |
| 目的 | 解決多重繼承重複基類 | 實現運行期多態 |
| 關鍵字 | virtual 在繼承列表 | virtual 在成員函數宣告 |
| 運行期開銷 | 額外 vbptr/vbtable、間接存取 | vptr/vtable、間接呼叫 |
| 記憶體佈局影響 | 基類子物件位置不固定 | 類別增加 vptr |
現代 C++ 選型建議
- 避免多重繼承:優先用**組合 + 介面類別 (純虛類)** 設計。
- 若必須多重繼承:最多一個非虛基類含資料/狀態,其餘為**介面類別** (無資料成員、純虛函數)。
- 菱形繼承**必須用虛擬繼承**,否則語意錯誤、資源管理極難正確。
- 虛基類**不要包含非靜態資料成員**(或極少),僅作為介面協議;資料下放到具體派生類。
- 構造函數:**最派生類顯式初始化所有虛基類**,文檔化說明責任歸屬。
總結
- 多重繼承引入菱形問題:重複基類子物件 → 資料冗余、存取模糊、構造混亂。
- 虛擬繼承 (
virtual public Base) 強制共享單一虛基類子物件,由最派生類獨家構造。
- 實現機制:vbptr + vbtable 間接定位共享基類,帶來額外存取開銷與佈局複雜度。
- 虛擬繼承 ≠ 虛函數,前者解決繼承結構,後者解決多態行為。
- 實戰原則:**組合優於繼承,介面繼承優於實現繼承,虛擬繼承僅作菱形救援**。