虚函数必须在基类中用virtual显式声明,否则不触发动态绑定;纯虚函数使类成为抽象类;析构函数需virtual以防资源泄漏;多态须通过指针或引用,避免对象切片;vtable和vptr实现动态绑定;override和final提升安全性。

虚函数必须在基类中用 virtual 显式声明
不加 virtual 的成员函数即使签名相同,也不会触发动态绑定。编译器只看指针/引用的静态类型,调用的是该类型定义的版本,不是实际对象的类型。
常见错误是忘记在基类声明 virtual,或者误以为“子类重写就自动多态”——C++ 不自动推导,必须显式标记。
-
virtual只需出现在基类声明中,派生类重写时加不加都可(但建议加上,提高可读性) - 纯虚函数写法是
virtual void func() = 0;,含纯虚函数的类即抽象类,不能实例化 - 析构函数若可能通过基类指针删除派生类对象,必须声明为
virtual,否则派生类部分不会被析构
多态调用必须通过指针或引用,不能直接用对象
值传递会触发对象切片(slicing),丢失派生类特有成员和虚函数表信息,调用的永远是基类版本。
Base b = Derived(); // 切片!b 只剩 Base 部分 b.func(); // 静态绑定,调用 Base::func() Base* ptr = new Derived(); ptr->func(); // 动态绑定,调用 Derived::func()
- 只有
Base*或Base&才能触发动态绑定 -
Base对象本身、Base类型的局部变量、函数按值传参,全部走静态绑定 - 返回值也不能是基类对象(同样切片),应返回指针或智能指针
虚函数表(vtable)和虚指针(vptr)是实现核心
每个含虚函数的类编译时生成一张虚函数表(vtable),存放该类所有虚函数的地址;每个对象实例开头隐式插入一个虚指针(vptr),指向其所属类的 vtable。
立即学习“C++免费学习笔记(深入)”;
运行时,通过 ptr->func() 调用时,编译器生成的代码实际是:(*ptr->vptr[索引])(ptr) —— 先取 vptr,再查表,最后调用。
- vtable 是类级别的,所有该类对象共享同一张表
- vptr 是对象级别的,在构造函数中由编译器自动初始化(先父后子,所以构造期间虚函数调用仍走当前类的 vtable)
- 虚函数调用比普通函数多一次内存寻址(vptr → vtable → 函数地址),有轻微开销,但现代 CPU 分支预测能缓解
override 和 final 关键字能避免常见误写
不加 override 时,拼错函数名、参数类型不匹配、const 修饰不一致,都会导致“看似重写实则新增”,失去多态效果且无编译错误。
class Base { virtual void foo(int) const; };
class Derived : public Base {
void foo(int) {} // 缺 const → 新函数,非重写!
void foo(int) const override; // 正确:编译器检查是否真能重写
void bar() const override; // 错误:Base 没有 bar → 编译失败
};-
override强制编译器验证:该函数是否确实重写了基类虚函数 -
final加在函数后(virtual void f() final;)禁止进一步重写;加在类后(class D final : B {})禁止继承 - 这两个关键字不改变语义,但极大提升接口意图清晰度和错误捕获能力
虚函数多态看似简单,真正容易出问题的地方在于:对象生命周期管理(尤其是 virtual 析构)、切片场景的误用、以及未加 override 导致的静默失效。这些都不是语法错误,而是逻辑陷阱,调试时很难一眼发现。











