封裝 ≠ 面向物件。VB6 封裝但無多態。**多態** = 「同一介面,多種實現,運行期根據物件真實型別自動選擇正確函數」。
class Vehicle { public: void Show() { cout << "Vehicle"; } };
class Car : public Vehicle { public: void Show() { cout << "Car"; } };
void Test(Vehicle &v) { v.Show(); } // 參數型別 Vehicle&
Vehicle v; Car c;
Test(v); // 輸出 "Vehicle" - 編譯期綁定 Vehicle::Show
Test(c); // 輸出 "Vehicle" - **切片/靜態綁定**,仍調用 Vehicle::Show!
**原因**:編譯器只見參數靜態型別 Vehicle&,直接生成 Vehicle::Show 調用指令。
class Vehicle { public: virtual void Show() { cout << "Vehicle"; } };
class Car : public Vehicle { public: void Show() override { cout << "Car"; } };
void Test(Vehicle &v) { v.Show(); }
Vehicle v; Car c;
Test(v); // "Vehicle"
Test(c); // "Car" - **動態綁定**,根據 v 綁定的真實物件型別分發
Vehicle 物件 (含 virtual):
+----------+ vtable (每類一份,唯讀段)
| vptr ───┼──→ +------------------+
+----------+ | &Vehicle::Show |
+------------------+
Car 物件:
+----------+ vtable (Car 專屬)
| vptr ───┼──→ +------------------+
| Vehicle | | &Car::Show | ← 覆寫槽位
| 部分 | +------------------+
| Car 部分 |
+----------+
vptr 指向該類別的 vtable。v.Show() 時,編譯器生成:vptr[0]() (假設 Show 是第 0 個虛函數)。vptr 指向真實物件類別的 vtable,間接跳轉到正確函數入口。| 規則 | 說明 |
|---|---|
| 簽名必須完全一致 | 參數型別、const、引用/值、返回型別 (協變返回除外) 必須匹配,否則隱藏而非覆寫 |
| 僅非靜態成員函數可虛 | 靜態函數無 this、構造函數物件未形成、內聯函數編譯期展開 |
| 析構函數應為 virtual | 基類指標 delete 派生物件時,確保調用派生析構 → 基類析構鏈 |
| 友元/運算子多載可虛 | 友元非成員函數不可虛;運算子多載為成員函數時可虛 |
| 私有虛函數可被覆寫 | 存取控制與虛機制正交,派生類可覆寫基類 private virtual |
class Base { public: virtual ~Base() { cout << "~Base"; } };
class Derived : public Base { public: ~Derived() { cout << "~Derived"; } };
Base *p = new Derived();
delete p; // 輸出: ~Derived ~Base (正確)
// 若 Base::~Base 非 virtual → 僅輸出 ~Base,Derived 資源洩漏!
class Base { public: virtual Base* Clone() { return new Base(*this); } };
class Derived : public Base { public: Derived* Clone() override { return new Derived(*this); } };
// 返回型別從 Base* 協變為 Derived*,型別安全且避免切片
override (C++11):編譯器驗證簽名匹配,防止拼寫錯誤導致隱藏。final:禁止進一步繼承/覆寫,編譯器可優化去虛擬化 (devirtualization)。
class File {
public:
void Open(string path) { // 非虛:統一檢查、日誌、鎖
if (!Validate(path)) throw invalid_argument("path");
lock_guard lk(mtx);
DoOpen(path); // 私有虛:派生類實現具體邏輯
Log("Opened: " + path);
}
virtual ~File() {}
private:
virtual void DoOpen(string path) = 0; // 派生類實現
mutex mtx;
};
class LocalFile : public File { void DoOpen(string path) override { /* ... */ } };
class RemoteFile : public File { void DoOpen(string path) override { /* ... */ } };
| 開銷來源 | 大小 | 優化手段 |
|---|---|---|
| 間接跳轉 (vptr→vtable→函數) | 1-2 CPU 指令 | 現代 CPU 分支預測極佳,熱點路徑幾乎零誤差 |
| 阻礙內聯 | 編譯器無法內聯虛調用 | final 允許 devirtualization + 內聯;PGO (Profile Guided Optimization) 亦可 |
| 物件體積增加 vptr (4/8 bytes) | 每含虛函數的類別實例 | 介面類別通常較少實例,影響可忽略 |
override 防錯、final 優化、NVI 模式封裝、構造/解構中禁虛調用**。final/PGO 消除間接跳轉。