多態是 C++ 物件導向三大支柱之一。本文透過 Animal / Fish 經典案例,示範:**無 virtual 時靜態綁定行為**、加上 virtual 後動態綁定效果、**vtable/vptr 記憶體模型**、virtual 與遲綁定 (晚期綁定) 的本質關係,並總結多態一句話精髓。
class Animal {
public:
void breathe() { cout << "animal breathe\n"; }
};
class Fish : public Animal {
public:
void breathe() { cout << "fish bubble\n"; }
};
void fn(Animal* pAn) { pAn->breathe(); }
int main() {
Fish fh;
Animal* pAn = &fh; // 隱含向上轉型
fn(pAn); // 輸出: animal breathe
}
pAn 型別為 Animal*,編譯器**編譯期**決定呼叫 Animal::breathe() (早期綁定)。Fish* 視為 Animal*,物件記憶體「切片」為基類部分,vptr 指向 Animal vtable。
class Animal {
public:
virtual void breathe() { cout << "animal breathe\n"; } // 關鍵字 virtual
};
class Fish : public Animal {
public:
void breathe() { cout << "fish bubble\n"; } // 覆寫
};
// main 相同
// 輸出: fish bubble
virtual 函數的類別建立 **vtable** (虛函數表),儲存函數位址。pAn->breathe() 時:pAn->vptr->vtable[breathe_offset](),依**物件實際型別** (Fish) 解析到 Fish::breathe。
Fish 物件記憶體:
+------------------+
| vptr ────────────┼──→ Animal vtable [breathe → Animal::breathe]
| Animal 成員 | Fish vtable [breathe → Fish::breathe]
| Fish 成員 |
+------------------+
向上轉型 Animal* pAn = &fish:
pAn 指向同一位址,但編譯器視為 Animal 部分。
若無 virtual:直接呼叫 Animal::breathe (靜態)。
若有 virtual:pAn->vptr 指向 Fish vtable → 動態呼叫 Fish::breathe。
| 特性 | 早期綁定 (無 virtual) | 遲綁定 (有 virtual) |
|---|---|---|
| 決定呼叫時機 | 編譯期 | 運行期 |
| 依據 | 指標/參考**宣告型別** | 物件**實際型別** (動態型別) |
| 效能 | 極快 (直接呼叫) | 極微額外開銷 (vptr 間接查表) |
| 多態支援 | 無 | 有 |
「在基類函數前加 virtual,在衍生類**重寫** (覆寫) 該函數,運行時將依**物件實際型別**呼叫對應版本:衍生類物件呼叫衍生類版本,基類物件呼叫基類版本。」
virtual ~Base(),否則 delete basePtr 僅呼叫基類解構函數,衍生類資源洩漏。virtual,衍生類同名同參函數自動為虛函數 (建議加 override 讓編譯器檢查)。virtual void foo() = 0; 強制衍生類實作,構成抽象類別 (不可實例化)。virtual 關鍵字啟用 **vtable/vptr 機制**,實現**運行期動態綁定**,是 C++ 多態的基石。理解「指標型別 vs 物件型別」、「早期 vs 遲綁定」、「vtable 記憶體佈局」,能正確設計多態介面、避免建構期虛函數陷阱、正確實作虛解構函數確保資源安全釋放。