基类析构函数必须是虚函数,否则通过基类指针删除派生类对象时仅调用基类析构,导致派生类资源未释放而引发内存或资源泄漏;标准规定此行为为未定义行为。

为什么基类析构函数必须是虚函数
当你用 new 创建一个派生类对象,却通过基类指针删除它(delete base_ptr),如果基类析构函数不是虚函数,C++ 只会调用基类的析构函数,派生类自己的析构逻辑(比如释放 new 出来的成员、关闭文件句柄、解注册回调)根本不会执行——这直接导致内存泄漏或资源泄露。
这不是“可能出问题”,而是标准明确规定的行为:非虚析构 + 多态删除 = 未定义行为(UB),常见表现是程序没报错但内存持续增长,或者崩溃在奇怪的位置。
怎么加虚析构:语法和位置
虚析构函数写法很简单,但必须加在基类里,且通常声明为 virtual ~Base() = default; 或带空实现。注意两点:
- 派生类析构函数自动继承虚性,无需再写
virtual(写了也不报错,但冗余) - 如果基类有虚函数(比如
virtual void draw()),那它本就应该有虚析构;没有虚函数但又打算被继承并多态删除的类,也必须加 - 纯虚析构函数可以存在,但必须提供定义:
virtual ~Base() = 0;后面得跟Base::~Base() {}
示例:
立即学习“C++免费学习笔记(深入)”;
class Base {
public:
virtual ~Base() = default; // ✅ 关键就这一行
};
<p>class Derived : public Base {
int* data = new int[100];
public:
~Derived() override { delete[] data; } // 自动虚,不用写 virtual
};不加虚析构的典型错误现场
最容易踩坑的是容器存指针、工厂函数返回基类指针、以及用智能指针但没配对自定义删除器的场景:
-
std::vector<base> vec; vec.push_back(new Derived); ... for (auto p : vec) delete p;→ 派生类析构不执行 - 工厂函数返回
std::unique_ptr<base>,但基类析构非虚 →unique_ptr析构时仍只调基类 - 把派生类对象地址赋给
Base*后,直接delete—— 即使你确定当前对象是派生类,只要类型是Base*,编译器就按非虚处理
这些情况不会编译报错,运行时也未必立刻崩,但资源泄漏往往在长期运行或压力测试中才暴露。
虚析构带来的开销和例外
虚析构函数会让类变成多态类型,增加一个虚函数表指针(通常 8 字节),所有对象实例都多占这点空间。但这是必要代价,别为了省几个字节去赌“我永远不会多态删除”。
真正可以不加虚析构的只有两种情况:
- 类明确设计为不可继承(加
final,如class Base final { ... };) - 类纯粹作接口聚合(比如只含
static成员或类型别名),根本不分配堆内存,也不持有任何需清理的资源
哪怕只是“暂时没写派生类”,只要头文件公开、别人可能继承,就必须加虚析构——C++ 不做假设,只认定义。









