虚函数仅在通过基类指针或引用调用时触发动态绑定,否则无意义;析构函数须为virtual以防资源泄漏;override比virtual更关键,可捕获重写错误;纯虚函数用于强制接口契约,非泛用。

只有当这个函数**需要通过基类指针或引用调用、且希望它在运行时根据实际对象类型跳转到派生类版本**时,才需要声明为 virtual。
基类指针/引用调用是虚函数生效的唯一前提
虚函数不是“只要写了就能多态”,它只在特定调用方式下起作用。如果你从不使用 Base* 或 Base& 持有派生类对象,那加 virtual 完全没意义——编译器根本不会走虚表查找路径。
- ✅ 正确场景:函数作为接口被上层框架或容器统一管理(比如
std::vector<:unique_ptr>></:unique_ptr>中遍历调用draw()) - ❌ 无意义场景:只在派生类内部调用,或始终用具体类型调用(如
Circle c; c.calcArea();) - ⚠️ 常见误判:以为“以后可能继承”就提前加
virtual——这会增加对象内存开销(每个对象多一个vptr),也破坏封装意图
析构函数必须是虚函数的两种情况
这是最常被忽略但后果最严重的虚函数使用点:只要类设计为被继承,且你打算用基类指针去 delete 派生类对象,析构函数就必须是 virtual。
- 不加
virtual的后果:delete ptr;只调用基类析构,派生类部分内存泄漏、资源未释放(如文件句柄、线程、GPU buffer) - 即使基类析构函数为空,也要写
virtual ~Base() = default;——空函数不等于无析构逻辑 - 如果类明确禁止继承(比如加了
final),那析构函数可不加virtual
override 关键字比 virtual 更值得优先检查
现代 C++(C++11 起)中,override 的实际价值远高于盲目加 virtual。它能帮你立刻发现三类典型错误:
立即学习“C++免费学习笔记(深入)”;
- 参数类型不一致(比如基类是
void f(int),派生类写成void f(double)→ 隐式隐藏而非重写) - const 限定符缺失(基类
void f() const,派生类void f()→ 编译失败) - 返回类型协变失效(比如基类返回
Base*,派生类返回Derived*但没满足继承关系)
所以更务实的做法是:先在基类中谨慎加 virtual,然后在所有派生类重写处强制用 override —— 编译器报错比运行时行为诡异好 debug 多了。
纯虚函数和接口抽象的边界在哪里
当基类本身不该被实例化,且某个函数在所有派生类中**必须提供不同实现**时,才用纯虚函数(= 0)。这不是为了“看起来更面向对象”,而是表达一种契约:
- ✅ 合理:图形类的
draw()、网络协议的serialize()、插件系统的onEvent() - ❌ 过度:把
getName()、getId()这类通用 getter 全设为纯虚 —— 大多数派生类其实可以共享默认实现 - ⚠️ 注意:含纯虚函数的类是抽象类,不能直接构造;但它的子类只要实现了全部纯虚函数,就可以实例化
虚函数机制本身不复杂,难的是判断“这里到底需不需要多态”。真正容易出问题的,从来不是语法写错,而是把本该静态绑定的逻辑强行塞进虚表,或者该用 override 检查的地方手滑漏掉 const —— 这些细节在调试时往往藏得极深。


















