虚函数调用依赖vtable和vptr实现动态绑定,必须通过指针或引用触发;析构函数须声明为virtual以防资源泄漏;override和final可防止重写错误。

虚函数调用依赖 vtable 和 vptr,不是语法糖
虚函数实现多态不是靠编译器“记住类型”或运行时查名字,而是靠每个含虚函数的类生成一张 vtable(虚函数表),每个对象头隐式存一个 vptr(指向所属类的 vtable)。调用 obj->func() 时,实际是:取 vptr → 查 vtable 中对应偏移 → 跳转到真实函数地址。
这意味着:没有虚函数的类不生成 vtable;派生类若重写虚函数,会覆盖基类 vtable 中该函数指针;若只新增虚函数,vtable 长度增加,但基类部分保持不变。
必须用指针或引用才能触发动态绑定
直接用对象值传递或局部变量声明,会触发对象切片(slicing)或静态绑定,虚函数机制完全失效。比如:
Base b = Derived(); // 切片!b 是纯 Base 对象,无 Derived 的 vtable b.func(); // 调用 Base::func,非虚调用
正确方式只有两种:
- 通过
Base*或Base&持有子类实例(推荐堆分配 +std::unique_ptr<base>) - 确保调用发生在虚函数内部(如模板回调中传入 this,仍走 vtable)
常见错误:把 std::vector<base> 当作多态容器——它存的是拷贝后的 Base 值,所有虚调用都退化为基类实现。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
析构函数必须声明为 virtual
否则 delete 指向派生类对象的 Base* 时,只会调用 Base::~Base(),派生类部分内存泄漏且资源不释放。这不是风格问题,是 UB(未定义行为)。
规则很简单:
- 只要类设计为被继承(哪怕只是接口类),
~Base()必须加virtual - 如果类明确禁止继承(C++11 起可用
final),可不加,但需确保所有子类都不存在 -
virtual ~Base() = default;是安全、零开销的惯用写法
override 和 final 关键字能防住多数误用
没加 override 时,拼错函数名、参数类型不匹配(如 int vs const int&)、遗漏 const 修饰符,都会导致“看似重写实则新增”,调用时走基类逻辑,调试极难定位。
加上后编译器强制校验:
-
void func() override;—— 编译失败说明基类根本没有匹配的虚函数 -
void func() final;—— 禁止进一步派生类重写,适合稳定接口 - 搭配
-Woverloaded-virtual(GCC/Clang)还能捕获隐藏而非重写的函数
真正容易被忽略的是:即使你写了 virtual,不加 override 就等于裸奔。现代 C++ 项目里,漏掉 override 应该和忘记 delete 堆内存一样被 CI 拦住。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










