虚函数必须在基类中用virtual显式声明,否则不构成多态;派生类重写需用override确保正确性;含虚函数的类析构函数必须为virtual,否则delete基类指针时派生类析构逻辑不执行。

虚函数必须用 virtual 显式声明,否则不构成多态
在基类中不加 virtual 的函数,哪怕派生类里写了同名同参函数,调用时也只看指针/引用的静态类型,不会动态绑定。这是最常踩的坑:以为“函数名一样+继承了”就自动多态,结果发现 Base* p = new Derived(); p->func(); 调的还是 Base::func()。
实操建议:
立即学习“C++免费学习笔记(深入)”;
-
virtual必须写在基类函数声明处(头文件里),不能只在定义(.cpp)里加 - 派生类重写时可加
override(C++11 起),编译器会检查是否真有对应虚函数,避免拼错名或参数不匹配 - 纯虚函数写成
virtual void func() = 0;,含纯虚函数的类无法实例化,强制派生类实现
析构函数不加 virtual 会导致资源泄漏
如果基类析构函数不是虚函数,用 Base* p = new Derived(); delete p; 时,只会调 Base::~Base(),Derived 的析构逻辑(比如释放内存、关文件句柄)完全不会执行——这不是“没调对”,是根本不会进派生类析构函数。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 只要类设计为被继承(尤其有子类要 new/delete),基类析构函数必须声明为
virtual - 即使基类析构函数函数体为空,也要写
virtual ~Base() = default;或virtual ~Base() {} - 用智能指针(如
std::unique_ptr<base>)也不能绕过这点:delete 操作仍依赖虚析构
虚函数表(vtable)是编译器自动生成的指针数组,不是 C++ 标准但所有主流实现都用
每个含虚函数的类,编译器会在其对象内存布局开头塞一个隐藏指针(vptr),指向该类唯一的虚函数表。调用虚函数时,实际是通过 vptr 查表跳转——所以虚函数调用比普通函数多一次内存读取,但现代 CPU 分支预测很成熟,开销几乎可忽略。
关键影响:
- 虚函数会让类失去 POD 类型属性,影响 memcpy、memset、union 使用等底层操作
- 对象大小会增加(通常是 8 字节,64 位系统下
vptr大小) - 模板特化或
constexpr函数里不能出现虚函数调用,因为绑定发生在运行时
多态生效的前提是“通过指针或引用调用”,直接用对象调用不触发虚机制
Derived d; d.func(); —— 这永远调 Derived::func(),哪怕 func() 是虚函数;而 Base& b = d; b.func(); 才走虚调用流程。很多人把多态理解成“函数能被重写”,忽略了调用方式这个硬性前提。
常见错误现象:
- 传值返回派生类对象,接收到基类对象后调用虚函数,结果是基类实现(因为发生了切片,
vptr已被替换成基类的) - 函数参数写成
void foo(Base b)(传值),应改为void foo(const Base& b)或void foo(Base* b) - std::vector
存派生类对象,会切片,必须用 std::vector<:unique_ptr>></:unique_ptr>
虚函数机制本身不复杂,真正容易出问题的地方,往往藏在对象生命周期管理、传参方式和内存布局这些看似“外围”的环节里。










