**多重繼承** 允許一個派生類擁有多個直接基類。當多條繼承路徑匯聚同一基類時,會產生**菱形繼承**,導致基類子對象重複。C++ 提供 **虛繼承** (`virtual` 關鍵字) 解決此問題,但引入額外的間接訪問開銷與構造規則複雜性。本文全面剖析多重繼承內存佈局、虛繼承機制、構造/析構順序、指針轉換陷阱。
class Vehicle { // 基類
int weight;
public:
Vehicle(int w = 0) : weight(w) {}
virtual void ShowMe() = 0;
void SetWeight(int w) { weight = w; }
};
class Car : public Vehicle { // 路徑 1
int aird;
public:
Car(int w, int a) : Vehicle(w), aird(a) {}
void ShowMe() override { cout << "Car\n"; }
};
class Boat : public Vehicle { // 路徑 2
float tonnage;
public:
Boat(int w, float t) : Vehicle(w), tonnage(t) {}
void ShowMe() override { cout << "Boat\n"; }
};
class AmphibianCar : public Car, public Boat { // 菱形頂點
public:
AmphibianCar(int w, int a, float t)
: Vehicle(w), Car(w, a), Boat(w, t) {} // 必須顯式調用 Vehicle 構造
void ShowMe() override { cout << "AmphibianCar\n"; }
};
AmphibianCar 內存中包含 **兩個** `Vehicle::weight`:
AmphibianCar 佈局 (非虛繼承): +------------------+ | Car 部分 | | vptr → Car::vtable | | Vehicle::weight (副本 1) | | Car::aird | +------------------+ | Boat 部分 | | vptr → Boat::vtable | | Vehicle::weight (副本 2) | | Boat::tonnage | +------------------+ | AmphibianCar 部分 | +------------------+
調用 `a.SetWeight(3)` **編譯錯誤:歧義** — 編譯器不知調用哪條路徑的 `Vehicle::SetWeight`。
class Car : virtual public Vehicle { ... }; // 加上 virtual
class Boat : virtual public Vehicle { ... };
// AmphibianCar 定義不變
AmphibianCar 佈局 (虛繼承): +------------------+ | Car 部分 | | vptr → Car::vtable | | vbptr → Vehicle 虛基類表 (偏移量) | | Car::aird | +------------------+ | Boat 部分 | | vptr → Boat::vtable | | vbptr → Vehicle 虛基類表 | | Boat::tonnage | +------------------+ | Vehicle (共享) | | Vehicle::weight | +------------------+ | AmphibianCar 部分 | +------------------+
⚠️ 若最派生類**未**在初始化列表顯式調用虛基類構造函數,將調用虛基類**默認構造函數** (無參) — 若無默認構造則編譯錯誤。
| 轉換 | 非虛繼承 | 虛繼承 |
|---|---|---|
| 派生* → 某直接基類* | 固定偏移量調整 `this` | 固定偏移量 (含 vbptr) |
| 派生* → 虛基類* | N/A (無虛基類) | **運行時通過 vbptr 查表計算偏移** |
| void* → 派生* (多重繼承) | 需 `static_cast` 知曉哪條路徑 | 同左,虛基類更複雜 |
因此:**永遠用 `static_cast`/`dynamic_cast` 進行多重繼承指針轉換,絕不直接用 C 風格強轉或 `reinterpret_cast`。
多重繼承與虛繼承是 C++ 物件模型中最複雜的角落。深入理解其內存佈局、構造語義、指針調整機制,是驗證 C++ 開發者內功深淺的試金石 — 實戰中**慎用、少用、不用**。