多型是 C++ 物件導向核心特徵,動態聯編是其技術基石。本文從靜態聯編侷限出發,推導動態聯編必要性,實驗驗證含虛函數物件的額外記憶體佈局,揭示 vptr/vtable 機制起源。
virtual 觸發動態
// 1.cpp
class shape { void draw() { cout << "I am shape"; } void fun() { draw(); } };
class circle : public shape { void draw() { cout << "I am circle"; } };
circle oneshape;
oneshape.fun(); // 輸出 "I am shape" ❌
fun() 內呼叫 draw(),編譯期只知 this 為 shape*,靜態聯編至 shape::draw()。
// 2.cpp
class shape { virtual void draw() { cout << "I am shape"; } void fun() { draw(); } };
class circle : public shape { void draw() { cout << "I am circle"; } };
circle oneshape;
fun(&oneshape); // 輸出 "I am circle" ✅
基類 virtual 宣告 → 編譯器安裝動態聯編機制 → 執行期依物件型別分派。
編譯期不知具體衍生類型 → 必須在物件內嵌入型別資訊 → 執行期查閱分派。
// 3.cpp
class shape_novirtual { int a; void draw(); };
class shape_virtual1 { int a; virtual void draw(); };
class shape_virtual2 { int a; virtual void draw(); virtual void draw1(); };
sizeof(int) → 4
sizeof(shape_novirtual) → 4 (僅 int a)
sizeof(void*) → 4 (指標大小)
sizeof(shape_virtual1) → 8 (+4 bytes = 1 個指標)
sizeof(shape_virtual2) → 8 (+4 bytes = 仍為 1 個指標)
結論:含虛函數類別物件額外增加 1 個指標大小 (vptr),不隨虛函數數量增長。
class empty_novirtual {}; // sizeof = 1 (C++ 禁止零大小物件)
class empty_virtual { virtual void f(); }; // sizeof = 4 (vptr)
無成員變數時,編譯器插入 1 byte 佔位符;有虛函數則以 vptr 取代佔位符 (對齊到指標大小)。
this->virtual_func() → 編譯為 call this->vptr[index]this 實際型別決定 vtable → 正確函數位址 → 動態分派下篇將詳細剖析:vtable 具體佈局、衍生類別 vtable 繼承規則、編譯器靜態/動態分工、多重繼承下的 vptr 複雜度、thunk 調整器機制。