如果基类没有虚析构函数则是未定义行为UB标准依据是 [expr.delete]/3当 delete 的对象的静态类型与其动态类型不同时静态类型必须是动态类型的基类且静态类型必须有虚析构函数否则行为未定义。典型的实际后果派生类析构函数不会被调用编译器通常只调用静态类型基类的析构函数。派生类自身的清理代码不执行其成员对象如 std::string、文件句柄、RAII 封装等的析构函数也不会被调用造成资源泄漏。堆内存状态可能被破坏很多实现会把 sizeof(基类) 传给 operator delete而实际分配的是 sizeof(派生类)某些内存分配器对此敏感可能导致堆元数据损坏MSVC 调试模式下常报 _CrtIsValidHeapPointer 断言失败或 heap corruption。多继承 / 虚继承下更严重如果基类不是派生类布局中的首位置例如非首个基类或有虚基类基类指针与对象起始地址之间有一个偏移。delete 会把偏移后的错误地址交给 operator delete直接导致崩溃或堆损坏——这在实践中几乎必然出问题。编译器优化下的任意行为因为是 UB编译器可以假设这种情况从不发生。在优化开启时程序行为可能完全不可预测而不只是“泄漏一点资源”。不构成 UB 的例外情形被删除对象的静态类型与动态类型相同delete 精确类型的对象不需要虚析构。注意即使派生类只是空类、析构是平凡的通过基类指针删除在标准意义上仍然是 UB只是实践中常常“碰巧没事”——这是不可依赖的。正确做法Herb Sutter 准则基类析构函数要么是public virtual允许通过基类指针多态删除要么是protected 非虚禁止通过基类指针删除强制调用方持有派生类型指针。示例structBase{virtual~Base()default;// 方案一protected:~Base()default;// 方案二二选一};structDerived:Base{std::string s;};Base*pnewDerived;deletep;// 有了虚析构先调 ~Derived 再调 ~Base安全有了虚析构函数后delete 会通过虚表正确地先析构派生部分再析构基类部分并传递正确的指针和大小给 operator delete。
阅读完成 · 觉得有帮助?