虚函数需声明为virtual且通过指针或引用调用才触发动态绑定;普通对象调用、非virtual函数、静态函数均不进入虚表;基类必须显式加virtual,派生类推荐用override;构造函数不可为虚,virtual析构函数至关重要;纯虚函数写作=0,含纯虚函数的类为抽象类;每个含虚函数的类有唯一vtable,由编译器生成于只读段,对象头存vptr指向它;多重继承下可能有多个vptr;虚调用本质是vptr→vtable→函数地址跳转;签名不一致(如const差异)会导致隐藏而非重写,使动态绑定失效;override可避免误写,final防止进一步重写;-woverloaded-virtual可检测假重写;虚函数调用开销小但禁内联,vtable布局属abi细节,增删虚函数易致二进制不兼容;虚函数不应声明为inline;特殊成员函数默认不虚;动态绑定成立条件仅三点:指针/引用调用、函数为virtual、签名严格匹配。

虚函数怎么写才触发动态绑定
只有声明为 virtual 的成员函数,且通过指针或引用调用时,才会走运行时绑定。普通对象直接调用、非 virtual 函数、静态成员函数,统统不进虚表。
- 基类函数必须显式加
virtual,派生类重写时加不加virtual都行(C++11 起推荐加override) - 构造函数和析构函数不能是虚函数——但
virtual析构函数非常关键,否则delete派生类指针时只调基类析构,资源泄漏 - 纯虚函数写法是
= 0,含纯虚函数的类就是抽象类,不能实例化
虚函数表(vtable)在哪儿?谁来填?
每个含虚函数的类(不是每个对象)有一张静态 vtable,由编译器生成并存放在只读数据段。每个对象开头隐式存一个指向该类 vtable 的指针(vptr),构造时由构造函数自动初始化。
- 派生类 vtable 会继承基类部分,再覆盖重写的函数地址,新增的虚函数追加在末尾
- 多重继承下,对象内存布局更复杂:可能有多个
vptr,分别对应不同基类子对象 - 虚函数调用本质是:取
this->vptr→ 查 vtable 某偏移 → 跳转到函数地址。比普通函数调用多一次内存读 + 间接跳转
为什么 override 不写或写错会导致静多态失效
常见错误是签名不一致:参数类型、const 限定、引用/值传递差异,哪怕只差一个 const,编译器就当它是新函数,不是重写——于是调用时按静态类型找函数,根本不会查虚表。
- 漏写
override可能让你误以为重写了,实际是隐藏(hiding)了基类函数 -
final可以防止被进一步重写,适合明确不希望子类干预的虚函数 - 用
-Woverloaded-virtual(GCC/Clang)能捕获签名不匹配的“假重写”
虚函数调用性能和 ABI 兼容性要注意什么
虚函数调用本身开销不大,但破坏内联、阻碍某些优化(比如 devirtualization),在 hot path 上仍需谨慎。更大的坑是 ABI 层面:vtable 布局属于 ABI 细节,一旦类定义变更(如增删虚函数、改顺序),二进制接口就可能断裂。
立即学习“C++免费学习笔记(深入)”;
- 导出类给动态库时,避免在已有虚函数中间插入新虚函数;新增建议追加到末尾
- 不要把虚函数设为
inline——它本就不内联,还可能引发 ODR 违规 - 移动构造/赋值、
operator=等特殊成员函数默认不虚,除非你真需要多态语义,否则别动它们
虚表结构看不见摸不着,但只要记住:vtable 是类级别的、vptr 是对象头里的、调用是否走虚表只取决于「是否通过指针/引用」+「函数是否 virtual」+「签名是否严格匹配」——这三点没对齐,再多的 override 也没用。










