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

C++ 對象模型深度解析:虛函數表、單繼承/多重繼承/菱形繼承內存佈局

     編輯:關於C++

C++ 多態的核心實現機制是虛函數表。本文通過圖解與代碼實驗,詳細剖析單繼承、多重繼承、菱形繼承(含虛繼承)下的對象內存佈局、虛表指針分佈、指針調整偏移量等底層細節,幫助你建立精確的對象模型心智模型。

一、虛函數表基礎

  • 含虛函數的類,編譯器為其生成一張虛函數表(數組),存儲虛函數地址,表尾以 nullptr 結束。
  • 每個對象實例包含一個隱藏的 vptr(虛表指針),指向其類的虛表。
  • 派生類覆寫虛函數時,虛表對應槽位地址被替換為派生類版本,實現多態調度。
class Base {
public:
    Base() : _b(1) {}
    virtual void Fun1() { cout << "Base::Fun1\n"; }
    virtual void Fun2() { cout << "Base::Fun2\n"; }
private:
    int _b;
};

// 對象模型:
// Base 對象內存:[vptr] [_b]
// vptr → Base 虛表:[&Base::Fun1, &Base::Fun2, nullptr]

二、單繼承對象模型

派生類對象內存 = 基類部分 + 派生類新增成員。vptr 指向派生類虛表(覆寫函數已替換)。

class Derive : public Base {
public:
    Derive() : _d(2) {}
    virtual void Fun1() { cout << "Derive::Fun1\n"; } // 覆寫
    virtual void Fun3() { cout << "Derive::Fun3\n"; } // 新增
private:
    int _d;
};

// Derive 對象內存:[vptr] [_b] [_d]
// vptr → Derive 虛表:[&Derive::Fun1, &Base::Fun2, &Derive::Fun3, nullptr]

基類指針/引用指向派生對象時,vptr 自然指向派生虛表,調用派生版本——多態本質。

三、多重繼承對象模型

派生類包含多個基類子對象,每個基類帶自己的 vptr。派生類對象內存按聲明順序拼接基類子對象,再加自身成員。

class Base1 { virtual void Fun1(); int _b1; };
class Base2 { virtual void Fun2(); int _b2; };
class Derive : public Base1, public Base2 {
    virtual void Fun1(); // 覆寫 Base1
    virtual void Fun3(); // 新增
    int _d;
};

// Derive 內存佈局:
// [Base1 vptr] [_b1] [Base2 vptr] [_b2] [_d]
// Base1 vptr → Derive::Base1 虛表(含 Fun1 覆寫)
// Base2 vptr → Derive::Base2 虛表(含 Fun2)
// Derive 無單獨 vptr,複用第一個基類的 vptr 槽位

指針轉換偏移量:Base2* p = static_cast(derivePtr) 需加上 sizeof(Base1) 偏移。dynamic_cast 同理,但會運行期檢查。

四、菱形繼承(非虛繼承)

兩條路徑導致基類 Base 有兩份拷貝(Base1::Base 和 Base3::Base),數據冗餘且引發二義性。

class Base { virtual void Fun1(); int _b; };
class Base2 : public Base { virtual void Fun2(); int _b2; };
class Base3 : public Base { virtual void Fun3(); int _b3; };
class Derive : public Base2, public Base3 { virtual void Fun4(); int _d; };

// Derive 內存:兩份 Base 子對象
// [Base2部分: vptr, _b, _b2] [Base3部分: vptr, _b, _b3] [_d]
// 訪問 _b 需顯式限定:d.Base2::_b 或 d.Base3::_b

五、菱形虛繼承:共享單一基類子對象

virtual public Base 使 Base2/Base3 不再直接包含 Base 數據,而是通過 虛基類指針 或 偏移量表 間接訪問共享的 Base 實例(通常放在最末尾)。

class Base { virtual void Fun1(); int _b; };
class Base2 : virtual public Base { virtual void Fun2(); int _b2; };
class Base3 : virtual public Base { virtual void Fun3(); int _b3; };
class Derive : public Base2, public Base3 { virtual void Fun4(); int _d; };

// Derive 內存佈局(典型實現):
// [Base2: vptr, _b2, vbptr/offset] [Base3: vptr, _b3, vbptr/offset] [_d] [Base: vptr, _b]
// vbptr 指向虛基類表,記錄到共享 Base 的偏移量
  • 虛繼承引入額外間接訪問開銷(查虛基類表偏移量),但解決數據冗餘與二義性。
  • 構造順序:虛基類最先構造(由最派生類調用),再按聲明順序構造非虛基類,最後構造派生類。

六、實驗驗證代碼片段

typedef void (*Fun)();
void PrintVTable(Fun* vtable) {
    for(int i=0; vtable[i]; ++i) vtable[i]();
}

void Test() {
    Derive d;
    // 強制解析 vptr 位置(僅供教學演示,生產代碼禁止)
    Fun* vptr = *reinterpret_cast(&d);
    PrintVTable(vptr);
}

七、總結:關鍵圖景速記

場景vptr 數量基類拷貝份數關鍵機制
單繼承11虛表覆寫槽位
多重繼承等於直接基類數每個直接基類 1 份指針轉換需偏移調整
菱形非虛繼承等於葉子基類數多份(冗餘)二義性,需作用域限定
菱形虛繼承等於直接基類數1 份(共享)虛基類表/指針間接訪問

理解對象模型不僅是面試必考,更是調試多態異常、優化內存佈局、編寫高性能 C++ 代碼的基石。

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