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

C++ 多重繼承記憶體模型深度剖析:虛函數表、虛繼承與菱形問題

     編輯:關於C++

前言

C++ 為支援多型引入虛函數表 (vtable),為解決多重繼承的資料重複與二義性引入虛繼承。這使類別記憶體佈局呈現層次複雜性。本文由淺入深剖析三種情況。

情況一:無虛函數的多重繼承

配置規則

  1. 成員函數不佔記憶體。
  2. 同類別成員依宣告順序排列,無關存取權限。
  3. 基底類別成員排在衍生類別成員之前。
  4. 多個基底依繼承列表順序依序排列各自成員。

範例類別

class Top { int topVar; };
class LBase : public Top { int lVar; };
class RBase { int rVar; };
class Derived : public LBase, public RBase { int var1, var2; };

記憶體佈局

Derived 物件:
+----------------+
| Top::topVar    |  <- LBase 繼承自 Top
| LBase::lVar    |
+----------------+
| RBase::rVar    |  <- 第二個基底
+----------------+
| Derived::var1  |
| Derived::var2  |
+----------------+

無虛函數多重繼承記憶體模型

情況二:有虛函數的多重繼承

vtable 建構規則

  1. 基底類別所有虛函數依宣告順序進 vtable (含純虛函數)。
  2. 衍生類別繼承第一個有虛函數基底的 vtable,覆寫函數替換對應槽位。
  3. 衍生類別新增虛函數 追加在繼承的 vtable 之後。
  4. 後續基底類別各自擁有獨立 vtable,衍生物件包含多個 vptr。

範例

class Top {
    virtual void vTop() = 0;
    int topVar;
};
class LBase : public Top {
    virtual void vL() {}
    void vTop() override {}  // 實作純虛函數
    int lVar;
};
class RBase {
    virtual void vR() {}
    int rVar;
};
class Derived : public LBase, public RBase {
    void vL() override {}  // 覆寫
    int var1, var2;
};

記憶體佈局 (兩個 vptr)

Derived 物件:
+----------------+  <- LBase 子物件 (第一個基底)
| vptr → LBase vtable |  (含 Top::vTop, LBase::vL, Derived::vL)
| Top::topVar    |
| LBase::lVar    |
+----------------+
| vptr → RBase vtable |  <- RBase 子物件 (第二個基底)
| RBase::rVar    |
+----------------+
| Derived::var1  |
| Derived::var2  |
+----------------+

有虛函數多重繼承記憶體模型

情況三:虛繼承 (解決菱形問題)

菱形繼承問題

class B { int b; };
class X : public B { int x; };
class Y : public B { int y; };
class Z : public X, public Y { int z; };  // Z 含兩份 B::b → 二義性

虛繼承機制

  • virtual public B:基底 B 在最衍生類別中只有一份 (共享)。
  • 非虛繼承基底:每條路徑各有一份。
  • 虛基底子物件通常放在最衍生類別成員之後 (編譯器實作略異)。

修正版

class B { virtual void f() = 0; int b; };
class X : virtual public B { int x; void f() override {} };
class Y : virtual public B { int y; };
class Z : public X, public Y { int z; void f() override {} };  // 僅一份 B

記憶體佈局

Z 物件:
+----------------+
| vptr → X vtable |  (含 B::f, X::f, Z::f)
| X::x           |
+----------------+
| vptr → Y vtable |
| Y::y           |
+----------------+
| Z::z           |
+----------------+
| B::b           |  <- 共享虛基底,放在最後
+----------------+

虛繼承記憶體模型:虛基底放最後

關鍵總結

場景vptr 數量基底子物件份數虛基底位置
單一繼承 + 虛函數11N/A
多重繼承 + 虛函數每個有虛函數的基底一個每個基底一份N/A
虛繼承每個虛基底路徑一個虛基底共享 1 份通常在最衍生類別成員後

實務建議

  1. 盡量避免多重繼承,改用組合或介面類別 (純虛擬類別)。
  2. 若必須多重繼承,僅一個基底有實作/資料,其餘為介面 (純虛擬)。
  3. 菱形繼承必須用虛繼承,否則資料重複與二義性極難除錯。
  4. dynamic_cast、static_cast 跨基底轉型時,虛繼承影響指標調整邏輯。
  5. 記憶體佈局屬ABI 實作細節 (Itanium ABI / MSVC ABI 略異),不可依賴具體偏移量。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved