「虛函數能否內聯?」是 C++ 面試經典題。答案是:**能,但極受限制**。本文從物件模型、虛表機制、編譯器最佳化策略三個維度,深入剖析內聯虛函數的實際行為與最佳實踐。
| 呼叫情境 | 是否內聯 | 原因 |
|---|---|---|
具體物件直接呼叫 obj.func() | ✅ 幾乎總是內聯 | 編譯期確知動態型別,無需虛表查找 |
指標/參考呼叫 ptr->func() 且型別可推導 | ✅ 可能內聯 | 編譯器做逃逸分析/型別傳播成功時 |
| 指標/參考呼叫、型別不可知(跨模組、虛擬解構子) | ❌ 走虛表 | 必須保證多態正確性,編譯器拒絕冒險 |
取函數位址 &Class::func | ❌ 強制實體化 | 產生函數實體供位址運算使用 |
| 遞迴虛函數 | ❌ 無法內聯 | 編譯期無法展開無限深度 |
register 關鍵字)。final 類別、CRTP),直接內聯並省去 vptr 間接跳轉。out-of-line 實體,供無法內聯的呼叫點、虛表槽位、跨 DLL 邊界使用。class Base { public: virtual int f() { return 1; } };
class Derived : public Base { public: int f() override { return 2; } };
void test(Base* p) { p->f(); } // 虛表呼叫
void test2() { Derived d; d.f(); } // 直接內聯,mov eax, 2
用 g++ -O2 -S 或 cl /FA 產生組語,觀察 call vs mov eax, imm。
.cpp:減少頭文件依賴,讓編譯器自行決定是否內聯(LTO 啟用時跨檔也能內聯)。final 封鎖繼承:class Leaf final : public Base 讓編譯器敢於激進內聯所有虛函數。-flto / /GL):跨編譯單元內聯虛函數的關鍵。「內聯虛函數是『編譯器能做就做,不能做就拉倒』的最佳化,正確性永遠壓倒效能。寫給人看的代碼,請誠實表達設計意圖——虛函數就是為了多態。」