虚函数(virtual)

📅 2026/7/29 17:49:45 👁️ 阅读次数 📝 编程学习
虚函数(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 析构