多態是 C++ 物件導向的核心支柱,支援「以基類介面操作衍生類物件」的執行期動態綁定。其底層實現依賴虛函式表、虛函式指標、RTTI (Run-Time Type Information) 等編譯器生成的隱藏結構。本文從記憶體模型、虛表佈局、dynamic_cast 原理、多重繼承/虛繼承複雜性、效能與最佳實踐五維度,深度剖析 C++ 多態實現機制。
class Base {
public:
virtual void show() { std::cout << "Base\n"; }
virtual void foo() { std::cout << "Base::foo\n"; }
int b;
};
class Derived : public Base {
public:
void show() override { std::cout << "Derived\n"; } // 覆寫
void bar() { std::cout << "Derived::bar\n"; } // 新增虛函式
int d;
};
// Base 物件 (sizeof = 24)
[0] vptr ──────► Base 虛表
[8] b (int)
[12] padding (4 bytes)
// Derived 物件 (sizeof = 32)
[0] vptr ──────► Derived 虛表
[8] b (int) // 繼承自 Base
[12] padding (4)
[16] d (int)
[20] padding (4)
// Base 虛表 (靜態唯讀)
[0] &Base::show
[8] &Base::foo
[16] &Base::~Base (complete object destructor)
[24] &Base::~Base (deleting destructor)
[32] RTTI 指標 (type_info for Base)
// Derived 虛表
[0] &Derived::show // 覆寫:同槽位替換
[8] &Base::foo // 未覆寫:繼承基類槽位
[16] &Derived::bar // 新增:擴充虛表
[24] &Derived::~Derived
[32] &Derived::~Derived
[40] RTTI 指標 (type_info for Derived)
basePtr->show()。basePtr->vptr[0]()(取 vptr 指向的虛表第 0 槽位函式位址並呼叫)。basePtr 指向 Derived 物件,其 vptr 指向 Derived 虛表,槽位 0 為 &Derived::show → 執行衍生類實作。Base* bp = new Derived();
Derived* dp = dynamic_cast(bp); // 執行期檢查
// 實現邏輯 (簡化)
Derived* dynamic_cast(Derived*, Base* src) {
if (!src) return nullptr;
// 1. 取得 src 物件的 RTTI (via vptr -> vtable -> RTTI pointer)
const std::type_info* srcInfo = src->vptr->rtti;
const std::type_info* targetInfo = &typeid(Derived);
// 2. 遍歷繼承圖判斷 srcInfo 是否為 targetInfo 或其衍生
if (isDerived(srcInfo, targetInfo)) {
// 3. 計算位址偏移 (單一繼承通常為 0)
return static_cast(src);
}
return nullptr;
}
多重繼承下,dynamic_cast 需要根據 RTTI 完整繼承圖計算**位址偏移量**,虛繼承更需透過 **vbptr (虛基類指標)** 間接定位共享基類子物件。這也是 dynamic_cast 比 static_cast 慢的主因(需執行期走圖)。
// type_info 簡化結構
class type_info {
const char* name; // 型別名稱 (mangled)
// 繼承關係圖通常編碼於編譯器內部結構,不直接暴露
};
class A { virtual void f(); int a; };
class B { virtual void g(); int b; };
class C : public A, public B { virtual void h(); int c; };
// C 物件佈局
[vptr_A] // 指向 C 的 A-in-C 虛表
a
[vptr_B] // 指向 C 的 B-in-C 虛表 (第二個 vptr)
b
c
// sizeof(C) 通常 > sizeof(A)+sizeof(B) 因對齊與多 vptr
關鍵: 每個直接基類帶來一個 vptr,衍生類物件包含多個子物件,各有獨立虛表指標。轉型需調整 this 指標偏移 (static_cast 編譯期計算,dynamic_cast 執行期計算)。
class Base { int x; };
class A : virtual public Base { ... };
class B : virtual public Base { ... };
class Derived : public A, public B { ... };
// Derived 物件佈局
[vptr_A] [vptr_B] [vbptr] // vbptr 指向虛基類表
A 成員
B 成員
Base 成員 (共享單一實例)
Derived 成員
this -> vbptr -> vbase_offset -> base_member。| 操作 | 開銷來源 | 典型週期 (現代 CPU) | 優化建議 |
|---|---|---|---|
| 虛函式呼叫 | 間接跳轉 (vptr->vtable->slot)、分支預測失敗 | 3-10 cycles | 熱路徑改用 CRTP 靜態多型、final 關鍵字讓編譯器去虛化、傳值而非指標/參考 |
| dynamic_cast | RTTI 繼承圖遍歷、多重繼承偏移計算 | 20-100+ cycles | 避免熱路徑使用;改用型別標籤 + static_cast、Visitor 模式、variant |
| 虛解構子 | 虛呼叫 + 多重解構子 (complete/deleting) | 同虛函式 | 基類必須虛解構子;衍生類標記 final 減少虛表項目 |
| 虛繼承存取 | 雙重間接 (vbptr -> offset) | 額外 2-5 cycles | 避免虛繼承;改用組合、介面隔離 |
template
class Base {
public:
void interface() { static_cast(this)->implementation(); }
};
class Derived : public Base {
public:
void implementation() { /* ... */ }
};
// 使用:Base* ptr = new Derived(); ptr->interface(); // 內聯、零開銷
using Shape = std::variant;
void drawAll(const std::vector& shapes) {
for (const auto& s : shapes) {
std::visit([](const auto& shape) { shape.draw(); }, s);
}
}
// 編譯器生成跳躍表,效能接近虛函式,但型別安全、無繼承耦合
template
concept Drawable = requires(T t) { t.draw(); };
void render(Drawable auto&& obj) { obj.draw(); } // 編譯期檢查,零開銷
protected 非虛 + 文檔禁止刪除基類指標)。override (C++11),防止簽章不符導致隱藏而非覆寫。final,協助編譯器去虛化與內聯。std::shared_ptr 組合。dynamic_cast;改用 enum TypeTag + static_cast 或 Visitor。C++ 多態以「虛表指標 + 虛函式表 + RTTI」三大支柱實現執行期動態綁定,支援單一/多重/虛繼承完整物件模型。理解其記憶體佈局、指標調整、型別檢查機制,不僅能避免物件切割、切片、虛解構遺漏等經典 Bug,更能在效能敏感場景做出明智抉擇:**虛函式用於擴充點、穩定介面;靜態多型 (CRTP/Concepts/Variant) 用於熱路徑、高效能核心**。現代 C++ 提供豐富的零開銷抽象工具箱,讓我們能在「執行期靈活」與「編譯期效能」間找到最佳平衡點。