硬編碼:if-else、switch-case 直接寫死類型判斷,擴展性差。
軟編碼:利用多態,針對抽象介面編程,具體實現在運行期決定,實現開閉原則。
多態本質:將「做什麼」(介面)與「怎麼做」(實現)分離,客戶代碼只依賴抽象基類指標/引用。
class C0 { public: void Test() { cout << "C0::Test\n"; } };
C0 obj0; C0* p = &obj0;
p->Test();
反組裝碼:
mov ecx, dword ptr [ebp-0Ch] ; this 指標
call @ILT+160(C0::Test) ; 直接呼叫,編譯期已知地址
編譯期即確定函數地址,無運行期開銷。
class C1 { public: virtual void Test() { cout << "C1::Test\n"; } };
class C11 : public C1 { public: void Test() override { cout << "C11::Test\n"; } };
C11 obj11; C1* p = &obj11;
p->Test(); // 實際呼叫 C11::Test
反組裝碼:
lea edx, [ebp-14h] ; 取得 p 位址
mov dword ptr [ebp-1Ch], edx
mov eax, dword ptr [ebp-1Ch] ; eax = p (vptr)
mov edx, dword ptr [eax] ; edx = *p = 虛表地址
mov esi, esp
mov ecx, dword ptr [ebp-1Ch] ; this = p
call dword ptr [edx] ; 呼叫虛表第 0 槽函數
關鍵差異:call dword ptr [edx] 間接呼叫,運行期通過 vptr → vtable → slot 解析真實函數地址。
vptr 指向類的虛表。vptr 仍指向派生類虛表。obj->vptr[virtual_offset]()。
class C1 { public: virtual void Test() {} };
class C11 : public C1 { public: void Test() override {} };
class C12 : public C1 { public: void Test() override {} };
C11 o11_0, o11_1;
C12 o12_0;
printf("o11_0 addr: %p, vtable: %p\n", &o11_0, *(void**)&o11_0);
printf("o11_1 addr: %p, vtable: %p\n", &o11_1, *(void**)&o11_1);
printf("o12_0 addr: %p, vtable: %p\n", &o12_0, *(void**)&o12_0);
輸出:
o11_0 addr: 0x12ff7c, vtable: 0x432098 o11_1 addr: 0x12ff78, vtable: 0x432098 // 同類共享虛表 o12_0 addr: 0x12ff74, vtable: 0x4320a8 // 不同類各有虛表
結論:
vptr,解引用得虛表地址。(進階補充)多重繼承對象包含多個 vptr,各指向對應基類子物件的虛表。虛繼承引入 vbptr(虛基類表指標)解決菱形繼承共享基類問題。編譯器生成的 thunk 調整 this 指標後再跳轉實際函數。
| 維度 | 靜態綁定 | 動態綁定 |
|---|---|---|
| 速度 | 直接呼叫,可內聯,最快 | 間接呼叫,難內聯,額外 1-2 指令 |
| 彈性 | 編譯期固定 | 運行期多態,支援插件/腳本 |
| 體積 | 無額外數據 | 每對象 +1 指標,每類 +1 虛表 |
建議:預設非虛,只有明確需要運行期多態(如框架回調、插件架構、策略模式)才加 virtual。析構函數若類作為基類**必須**聲明 virtual。
override:C++11 起請務必加 override,編譯器會捕獲簽名不匹配錯誤。