含虛函數的類,對象首部為 vptr(指向虛表),緊隨其後為數據成員。虛表為編譯期生成的只讀數組,存虛函數入口地址,按聲明順序排列,以 nullptr 結尾。
class Base {
virtual void f1(); virtual void f2();
int data;
};
// 對象佈局:[vptr] [data]
// 虛表: [&Base::f1, &Base::f2, nullptr]
class Derived : public Base {
void f1() override; // 覆寫
virtual void f3(); // 新增
int data2;
};
Derived 對象:[vptr] [Base::data] [data2]
虛表:[&Derived::f1, &Base::f2, &Derived::f3, nullptr]
規則:先複製基類虛表,覆寫函數替換對應槽位,新虛函數追加在末尾。
class Base1 { virtual void f1(); virtual void f2(); int b1; };
class Base2 { virtual void f3(); virtual void f4(); int b2; };
class Derived : public Base1, public Base2 { virtual void f5(); int d; };
對象佈局:
[Base1 vptr] → Base1 虛表 [Base1::b1] [Base2 vptr] → Base2 虛表 [Base2::b2] [Derived::d]
Derived 擁有兩個 vptr,分別指向兩張獨立虛表。Base2 部分在內存中偏移 sizeof(Base1) 位置。
class Base1 { virtual void f1(); virtual void f2(); };
class Base2 { virtual void f1(); virtual void f3(); };
class Derived : public Base1, public Base2 {
void f1() override; // 同時覆寫兩個基類的 f1
void f3() override; // 覆寫 Base2::f3
};
關鍵現象:Derived::f1 地址會同時寫入兩張虛表的 f1 槽位。編譯器可能生成 thunk(躍板碼) 調整 this 指針偏移,確保以 Base1* 或 Base2* 調用時 this 正確指向完整對象。
class Base { virtual void f1(); int b; };
class Derived : virtual public Base { virtual void f2(); int d; };
對象佈局:
[vptr] → Derived 虛表(含 Base::f1, Derived::f2) [vbptr] → 虛基類表(存 Base 偏移) [Derived::d] [Base::b] // 共享、放在最末尾
vptr 指向合併後的虛表(基類虛函數+派生類虛函數)。vbptr 指向虛基類表,運行時查找 Base 真實偏移。| 繼承方式 | Base 子物件數 | 訪問歧義 | 空間開銷 |
|---|---|---|---|
| 普通多重繼承 | 2 份 | 需作用域限定 | 數據冗餘 |
| 虛繼承 | 1 份 | 無歧義 | +vbptr +間接訪問 |
# GDB print *(int**)obj # 取 vptr print ((void**)vptr)[0] # 第一個虛函數地址 info vtbl obj # MSVC 專用命令,直接列出虛表