You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非多态派生析构函数通过shared_ptr自动调用的可靠性及虚析构必要性问题

关于shared_ptr在基类析构非虚时的析构行为解答

先把你的代码和输出贴出来方便参考:

#include <memory>
#include <iostream>
using namespace std;
struct _A { ~_A() { cout << "~_A()" << endl; } };
struct A : public _A { ~A() { cout << "~A()" << endl; } };
int main(int argc, char** argv) {
    { shared_ptr<_A> p = make_shared<A>(); } //~A() called
    return 0;
}

程序输出:

~A() ~_A()

为什么shared_ptr能正确调用派生类析构?

这是shared_ptr的一个核心设计特性:当你通过make_shared<A>()创建对象并赋值给shared_ptr<_A>时,shared_ptr内部会保存一个和实际对象类型匹配的删除器,而非仅根据模板参数_A生成删除逻辑。

具体来说,make_shared会根据你传入的实际类型(这里是A)生成对应的删除逻辑——也就是调用delete static_cast<A*>(ptr),这个逻辑会被shared_ptr通过类型擦除的方式存储起来。当shared_ptr的引用计数归零触发析构时,它会调用这个预存的删除器,自然就能正确调用A的析构函数,进而自动触发基类_A的析构(派生类析构执行完毕后会默认调用基类析构)。

这种行为可靠吗?属于未定义行为吗?

完全可靠,绝对不属于未定义行为,这是C++标准明确规定的shared_ptr特性。只要你通过以下方式构造shared_ptr,它都能正确处理:

  • 使用make_shared创建派生类对象并赋值给基类shared_ptr
  • 直接用派生类的裸指针构造基类shared_ptr(比如shared_ptr<_A> p(new A);)

这些场景下,shared_ptr都会捕获到实际对象的类型,生成正确的删除器。

不使用裸指针,还需要把基类析构声明为virtual吗?

非常建议你还是把基类的析构函数声明为virtual,原因有两点:

  1. 避免其他场景的潜在错误:虽然shared_ptr能处理这种情况,但如果有人不小心用了其他智能指针(比如unique_ptr),问题就会出现。比如unique_ptr<_A> p = make_unique<A>();,此时unique_ptr的默认删除器是针对_A*的,会直接调用delete static_cast<_A*>(ptr),而因为基类析构非虚,这会导致未定义行为——派生类A的析构不会被调用,可能造成资源泄漏。
  2. 符合面向对象编程规范:如果一个类是作为基类被设计来继承的,把析构设为virtual是C++的常规做法。它能确保无论通过哪种方式(裸指针、智能指针)销毁派生类对象,都能正确调用完整的析构链,从根源上避免析构相关的bug。

总结一下:shared_ptr的这种行为是标准允许且可靠的,但为了代码的健壮性和兼容性,依然建议给作为基类的类声明virtual析构函数。

内容的提问来源于stack exchange,提问作者PinkTurtle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 20:37:37