非多态派生析构函数通过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,原因有两点:
- 避免其他场景的潜在错误:虽然shared_ptr能处理这种情况,但如果有人不小心用了其他智能指针(比如
unique_ptr),问题就会出现。比如unique_ptr<_A> p = make_unique<A>();,此时unique_ptr的默认删除器是针对_A*的,会直接调用delete static_cast<_A*>(ptr),而因为基类析构非虚,这会导致未定义行为——派生类A的析构不会被调用,可能造成资源泄漏。 - 符合面向对象编程规范:如果一个类是作为基类被设计来继承的,把析构设为virtual是C++的常规做法。它能确保无论通过哪种方式(裸指针、智能指针)销毁派生类对象,都能正确调用完整的析构链,从根源上避免析构相关的bug。
总结一下:shared_ptr的这种行为是标准允许且可靠的,但为了代码的健壮性和兼容性,依然建议给作为基类的类声明virtual析构函数。
内容的提问来源于stack exchange,提问作者PinkTurtle
相关产品推荐
相关产品推荐

