# C++ Virtual & vtable ## 1. 什麼是 virtual 函式 * **virtual**:讓函式支援**動態繫結(dynamic binding)** * 透過基底類別指標/參考呼叫時,會依物件**實際型別**決定呼叫哪個版本 ```cpp class Base { public: virtual void speak(); }; class Derived : public Base { public: void speak() override; }; ``` ## 2. vtable 與 vptr vtable(virtual table,虛擬函式表)(全域共享,非每個物件一份) 是 C++ 為了實現多型(polymorphism)而在編譯器層面建立的一張函式指標表。它的存在讓你可以用基底類別指標或參考去呼叫衍生類別覆寫過的函式,而不是固定呼叫基底類別版本。 * **vtable**(虛擬函式表):編譯器為每個有虛擬函式的類別建立的**函式位址表**(全域共享) * **vptr**(虛擬函式表指標):每個物件內部隱藏的指標,指向該物件型別的 vtable ### 呼叫流程 1. `obj->func()` 2. 編譯器透過物件的 **vptr** 找到 vtable 3. 從 vtable 中取出函式指標並呼叫 ``` 物件 → vptr → vtable → 函式位址 → call ``` ### 範例 ```cpp #include <iostream> using namespace std; class Base { public: virtual void speak() { cout << "Base speaking\n"; } }; class Derived : public Base { public: void speak() override { cout << "Derived speaking\n"; } }; int main() { Base* ptr = new Derived(); ptr->speak(); // 經由 vtable,呼叫 Derived::speak } ``` 執行步驟(簡化): ``` ptr (指向 Derived 物件) └── vptr → vtable_Derived └─ speak → &Derived::speak ``` ## 3. 記憶體配置與 vtable 示意(繼承時) 假設 64 位元系統(指標 8 bytes),將物件布局與 vtable 分別繪製,並以相同風格標註用途: ``` [ Derived 物件記憶體 ] ┌───────────────┐ ← 物件起始位址 │ vptr │ (指向 vtable_Derived) ├───────────────┤ │ baseData │ (Base 成員) │ padding │ (對齊) ├───────────────┤ │ derivedData │ (Derived 成員) │ padding │ (對齊) └───────────────┘ vtable_Derived: ┌───────────────────────┐ │ &Derived::speak │ ← 覆寫版本 │ &Derived::~Derived │ ← 虛擬解構子 │ &Base::~Base │ ← 解構鏈最後一步 └───────────────────────┘ ``` 1. `ptr->speak()` → 經由 vptr 找到 vtable\_Derived → 呼叫 `Derived::speak` 2. `delete ptr` → 經由 vptr 找到 `Derived::~Derived`,再呼叫 `Base::~Base` ## 4. 虛擬解構子(virtual destructor) * 目的:用基底指標刪除物件時,確保呼叫到衍生類別的解構子 * 實現:解構子位址存放在 vtable 中 ```cpp class Base { public: virtual ~Base(); }; ``` ## 5. 非 virtual 函式的情況 ### 差異:override vs. hiding | 類型 | virtual 函式覆寫 (override) | 非 virtual 函式重新定義 (hiding) | | ----- | -------------------------- | --------------------------- | | 判斷方式 | 執行期依物件**實際型別**決定呼叫版本(動態繫結) | 編譯期依**指標或參考型別**決定呼叫版本(靜態繫結) | | 呼叫方式 | 經由 vtable(虛擬函式表) | 直接呼叫函式位址 | | 效果 | 多型成立 | 多型不成立,基底版本被遮住 | | 編譯器檢查 | 可用 `override` 強制檢查 | 無法用 `override`,編譯器不檢查簽名錯誤 | ### 範例比較 ```cpp #include <iostream> using namespace std; class Base { public: virtual void speak() { cout << "Base speak\n"; } void hello() { cout << "Base hello\n"; } }; class Derived : public Base { public: void speak() override { cout << "Derived speak\n"; } // 覆寫 void hello() { cout << "Derived hello\n"; } // 隱藏 }; int main() { Base* b = new Derived(); b->speak(); // Derived speak(多型) b->hello(); // Base hello(靜態繫結,不是多型) } ``` 輸出: ``` Derived speak Base hello ``` ### 為什麼 non-virtual function 不能多型 * 多型依賴 vtable,而非虛擬函式**不會被放進 vtable** * 編譯器在編譯期就決定呼叫哪個函式 → 跟物件的實際型別無關 ### 小結 * **virtual + override** → 動態繫結,多型成立 * **non-virtual + 同名定義** → 隱藏,編譯期決定呼叫版本 * 想要基底指標呼叫到衍生版本 → 必須用 `virtual` ```cpp class Base { public: void hello(); // non-virtual }; class Derived : public Base { public: void hello(); // 隱藏 Base::hello }; ```
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up