虚函数实现运行时多态,通过vtable和vptr支持动态绑定;纯虚函数定义抽象接口;析构函数通常须声明为virtual以防内存泄漏。

虚函数让父类指针能调用子类重写的方法
没有 virtual,父类指针或引用调用函数时,编译器在编译期就绑定到父类版本(静态绑定);加了 virtual,运行时才根据实际对象类型决定调用哪个函数(动态绑定)。这是实现「接口统一、行为各异」的核心机制。
常见错误现象:
Base* p = new Derived(); p->func(); // 如果 func() 没声明为 virtual,调用的是 Base::func(),不是 Derived::func()
- 必须用指针或引用触发多态;直接用对象(如
Base b = Derived();)会切片,丢失子类信息 -
virtual只需在基类声明一次,派生类中override关键字可选但强烈建议加上,防止签名写错却没报错 - 构造函数里不能发生多态:基类构造期间,子类部分尚未初始化,
this被视为基类类型,即使调用虚函数也只会调用当前类的版本
vtable 和 vptr 是多态的底层支撑
C++ 编译器为每个含虚函数的类生成一张虚函数表(vtable),表中存的是该类所有虚函数的地址。每个该类的对象开头隐式插入一个指针(vptr),指向其所属类的 vtable。运行时通过 vptr 查表跳转,完成动态分派。
关键细节:
- 虚函数表是类级别的,不是对象级别的;所有同类型对象共享同一张表
- 子类继承父类
vtable,若重写虚函数,则覆盖对应槽位;新增虚函数则追加到表尾 - 多重继承下,对象内存布局更复杂,可能有多个
vptr,访问非第一个基类的虚函数需调整this指针偏移 - 虚函数调用比普通函数多一次内存读取(查
vtable)和一次间接跳转,但现代 CPU 分支预测通常缓解了这部分开销
纯虚函数强制派生类实现,构成抽象接口
声明为 virtual void func() = 0; 的函数叫纯虚函数,含纯虚函数的类叫抽象类,不能实例化。它不提供默认实现,只定义契约。
立即学习“C++免费学习笔记(深入)”;
使用场景:
- 建模「是什么」而非「怎么做」,比如
Shape类有virtual double area() = 0;,但具体由Circle、Rect自行实现 - 避免用户误用未完成的基类逻辑
- 注意:纯虚函数可以有定义(极少用),但只能通过作用域解析符显式调用,如
Base::func(),无法被自动动态绑定
析构函数几乎总是应该声明为 virtual
如果基类析构函数不是 virtual,用 Base* 删除派生类对象会导致子类部分内存泄漏——只调用了 Base::~Base(),而 Derived::~Derived() 根本不会执行。
正确写法:
class Base {
public:
virtual ~Base() = default; // 或空实现,但不能省略 virtual
};例外情况:类明确设计为不作基类(如 final 类)、或性能极端敏感且确定不会通过基类指针销毁对象时,可不加。但绝大多数带虚函数的基类,都应把析构设为 virtual。
容易忽略的一点:即使基类没有数据成员、只有虚函数,只要它可能作为多态基类被 delete,就必须有 virtual 析构——否则行为未定义,而编译器通常不报错。









