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 需加上 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 數量 | 基類拷貝份數 | 關鍵機制 |
|---|---|---|---|
| 單繼承 | 1 | 1 | 虛表覆寫槽位 |
| 多重繼承 | 等於直接基類數 | 每個直接基類 1 份 | 指針轉換需偏移調整 |
| 菱形非虛繼承 | 等於葉子基類數 | 多份(冗餘) | 二義性,需作用域限定 |
| 菱形虛繼承 | 等於直接基類數 | 1 份(共享) | 虛基類表/指針間接訪問 |
理解對象模型不僅是面試必考,更是調試多態異常、優化內存佈局、編寫高性能 C++ 代碼的基石。