虚函数(virtual)
虚函数(virtual)是基类指针/引用调用函数时,程序在运行时自动识别对象真实类型,执行子类重写的函数,而不是固定调用基类函数。
1.没有虚函数:静态绑定(早绑定)
编译器根据指针/引用的类型决定调用哪一个函数,编译阶段就确定好了。
class Bae{ public: void func(){ cout<<"基类函数"<<endl; } }; class Son:public Base{ public: void func(){ cout<<"子类函数"<<endl; } }; int main(){ Base*p=new Son() p->func();//没有virtual,永远调用 Base::func() }明明指向了子类对象,却执行了基类方法,无法实现不同子类不同行为。
2.加了virtual,动态绑定(晚绑定)
class Base{ public: virtual void func() { cout<<"基类函数"<<endl; } }; class Son:public Base{ public: void func() override { cout<<"子类函数"<<endl; } }; int main() { Base* p=new Son(); p->func();//运行时识别真实类型,调用 Son::func() }override是 C++11 引入的说明符,专门用于子类重写虚函数。
3.虚函数两大常见形态
3.1 普通虚函数
virtual void func();
基类提供默认实现,子类可选重写。
3.2 纯虚函数
virtual void SyncLog()=0;
- 基类没有函数实现
- 拥有纯虚函数的类=抽象类,不能直接实例化对象
- 子类必须重写该函数,否则子类依然是抽象类,无法创建对象
👉 作用:定义一套统一接口,强制所有子类遵守规范。
4.基类的析构函数加virtual的场景
不是所有基类都要加virtual析构!只有一种场景强制要求:使用基类指针/引用指向子类对象,并且会通过基类指针执行delete。
我们把场景分成两大类区分:
4.1 ✅必须写virtual ~Base()的场景
条件同时满足:
1.存在继承class Son:public Base
2.业务代码会:Base* p=new Son(); delete p;
典型例子
- 你当前的策略模式抽象基类
LogStrategy - 所有面向多态设计、作为接口的抽象类(含有纯虚函数)
这类类设计初衷就是让别人继承,并用基类指针管理子类对象,规范强制加上虚析构。
class LogStrategy { public: virtual ~LogStrategy() = default; // 必要! virtual void SyncLog(const std::string &msg) = 0; };4.2 ❌ 不需要、甚至不应该加 virtual 的场景
场景 1:不会通过基类指针 delete 子类
虽然有继承,但是永远不会执行delete 基类指针。 例如:对象全部定义在栈上、智能指针使用子类类型、不执行销毁动作。
Base obj; Son son; Base& ref = son; // 仅引用,不delete,不需要虚析构场景 2:这个类禁止被继承(C++11final)
class Test final {}; // final类不能被继承,完全不需要virtual析构场景 3:只是普通工具类,设计初衷就不允许别人继承
很多底层工具类,没有设计成多态基类,继承只是偶然行为。
C++ 编码规范(如 Google 规范):如果一个类不打算作为多态基类,不要随便加 virtual。 代价:产生虚表、增加对象内存开销。
场景 4:组合优先于继承,根本不设计继承关系
很多类不对外提供继承接口,自然无需考虑。
5.一条黄金判断口诀(写代码直接套用)
如果你不确定未来会不会有人通过基类指针 delete 子类 → 保守加上虚析构;如果你明确这个类不是用来做多态接口、禁止继承 → 不要加 virtual。
6.一个高频误区澄清
误区:含有虚函数的类,析构就必须 virtual?
错误! 含有普通virtual函数 ≠ 一定会用基类指针delete子类。 只是绝大多数含有虚函数的类,都是为了实现多态,极大概率会执行delete 基类指针,所以工程上约定俗成:含有虚函数的基类,一律写上 virtual 析构。
