C++:virtual function 怎么实现?
virtual function 能 inline 吗?

英文问法How are virtual functions implemented? · Can a virtual function be inline? · Why does a base class need a virtual destructor? · What does inline actually mean?

考过面经:NVIDIA DevTech NG 2026("CPP 八股 虚函数 inline",1p3a 1161658)· NVIDIA DL SWE 下的 AI infra 组电话面(C++ bug fixing、pointer、inheritance、multithreading,仅摘要)· NVIDIA 编译器实习(逐行判断调用的是哪个 constructor 和 destructor)· NVIDIA 框架岗 2023 onsite 第 5 轮(C++ 语言和一道 C++ 题,知乎)

先弄清楚:每个概念是什么

Virtual function 与 dynamic dispatch
通过 base 的 pointer 或 reference 调用时,执行的是 object 真实类型(dynamic type)的版本,而不是 pointer 声明的类型。例:Base* b = new Derived; b->f(); 调的是 Derived::f。
vtable 与 vptr
标准没规定怎么实现,主流 ABI 的做法是每个 polymorphic class 一张函数指针表(vtable),同类 object 共用,每个 object 开头藏一个指向它的指针(vptr)。virtual call = 读 vptr → 查表 → indirect call。例:x86-64 上 struct { int x; virtual void f(); } 的 sizeof 是 16(8 字节 vptr + int + padding),去掉 virtual 是 4。
inline
原意是建议编译器把函数体展开,现在的语义是允许多个 translation unit 里各有一份相同的定义,所以 header 里定义的函数要加。class 内定义的 member function 自动 inline。展不展开由编译器决定。
Devirtualization
编译器能确定 dynamic type 时,把 virtual call 换成直接调用,于是也能 inline。常见情况是直接对 object 调用、final、qualified call。
Virtual destructor 与 pure virtual
通过 base pointer delete 一个 derived object,base 的 destructor 不是 virtual 就是 undefined behavior。virtual void f() = 0; 是 pure virtual,带它的 class 是 abstract class,不能直接创建 object。

看图:b->f() 怎么查 vtable

objectvtable(每个 class 一张)函数代码Base objectBase 的 vtablevptr[0] Base::fBase::f:return 1~Base~Base()Derived objectDerived 的 vtablevptr[0] Derived::fDerived::f:return 2~Derived~Derived()①②delete bBase* bb->f()static type:Base*dynamic type:Derived①读 vptr(mov rax, [rdi])②取第 0 格,indirect jumpdelete b:同样读 vptr,查表调 ~DerivedDerived override 后换掉的格
图 1:b->f() 怎么经过 vptr 找到 Derived::f。Derived override 过的格(蓝色)换成了 Derived 的函数。橙红色实线是代码里 g1 的两条指令,先读 vptr,再取第 0 格 indirect jump。橙红色虚线是 delete b,同样经过 vptr 取到 ~Derived。

30 秒回答

A virtual call is dispatched on the object's dynamic type. Typical implementations give each polymorphic class a vtable of function pointers and put a vptr in every object, so a call through a base pointer loads the vptr, loads the slot and makes an indirect call. The main cost is that such a call usually can't be inlined. Today inline mainly means the function may be defined in several translation units; actual inlining is the compiler's choice. A virtual function can be inline, but it's only expanded when the compiler knows the dynamic type: a call on an object, a final class, or a qualified call. A polymorphic base needs a virtual destructor.

代码:什么时候能 devirtualize

struct Base {
  virtual int f() { return 1; }  // 类内定义 → 隐式 inline
  virtual ~Base() = default;     // 会被继承:destructor 要 virtual
};
struct Derived final : Base {
  int f() override { return 2; }
};
int g1(Base* b) { return b->f(); }        // 类型未知
int g2() { Derived d; return d.f(); }     // 类型已知
int g3(Derived* d) { return d->f(); }     // final
int g4(Base* b) { return b->Base::f(); }  // qualified call
// gcc / clang -O2:
// g1: mov rax, QWORD PTR [rdi]  ; 读 vptr(在 object 开头)
//     jmp [QWORD PTR [rax]]     ; 取 vtable 第 0 格,indirect jump
// g2、g3 → mov eax, 2;g4 → mov eax, 1(直接 inline)

必答点

追问

在 constructor / destructor 里调 virtual function 会调到谁?

调的是正在构造或析构的那个 class 自己的版本,因为这时 derived 部分还没构造或已经析构。这时 virtual call 一个 pure virtual function 是 UB。

constructor 能是 virtual 吗?

不能。分派要靠已经建好的 object 的 dynamic type,构造之前还没有。

virtual function 的默认参数按哪个类型取?

按 static type,编译期就替换好了。Base 写 f(int x = 1)、Derived 写 f(int x = 2),通过 Base* 调 f() 执行的是 Derived::f,但 x = 1。

来源cppreference: virtual function specifier(Explanation、In detail、Virtual destructor、During construction and destruction)· cppreference: inline specifier(Explanation)· cppreference: abstract class · cppreference: constructors · Itanium C++ ABI §1.1(virtual table 定义)、§2.4(vptr 在 offset 0)、§2.5(Virtual Table Layout)