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

析构函数非虚时shared_ptr与unique_ptr行为差异原因探究

shared_ptr与unique_ptr在非虚析构多态类中的析构行为差异

已知多态类未将析构函数设为虚函数会导致未定义行为,正确做法是将其设为虚函数。但在RHEL 8的gcc/g++环境下,使用shared_ptr销毁对象时能正常调用派生类析构函数,而unique_ptr却不行?

代码示例:

class Base
{
public:
    ~Base()
    {
        std::cout << "Base dtor" << std::endl;
    }
};

class Derrived : public Base
{
public:
    ~Derrived()
    {
        std::cout << "Derrived dtor" << std::endl;
    }
};

int main()
{
    // Derrived dtor NOT called! (I understand why!)
    {
        std::unique_ptr<Base> b = std::make_unique<Derrived>();
    }
    // Derrived and Base dtor called! WHY???
    {
        std::shared_ptr<Base> b = std::make_shared<Derrived>();
    }
}

工作中遇到shared_ptr隐藏内存泄漏的情况,换成unique_ptr后问题显现,修复虽简单但易遗漏。好奇两者行为差异的原因,shared_ptr是否会追踪对象的动态类型?曾将shared_ptr改为unique_ptr,预期行为一致但结果不同。


核心差异:删除器的绑定时机与存储方式

  • unique_ptr:编译期绑定删除器
    默认的std::unique_ptr<T>使用std::default_delete<T>作为删除器,这个删除器的类型是编译阶段确定的。当声明unique_ptr<Base>时,删除器就固定为default_delete<Base>,它只会调用Base的析构函数——不管实际指向的是派生类对象,编译时已经绑定了删除逻辑,运行时不会动态调整。
  • shared_ptr:运行时存储删除器
    shared_ptr在构造(比如通过make_shared<Derrived>)时,会捕获对象的动态类型,生成适配Derrived的删除器,并将其存储在内部的控制块中。当引用计数归零时,会调用这个运行时绑定的删除器,从而正确触发Derrived的析构,再自动调用基类析构。

为什么shared_ptr能“追踪”动态类型?

make_shared创建对象时明确知道实际构造的是Derrived类型,因此会生成对应类型的删除逻辑。当shared_ptr<Base>接收该对象时,控制块里已经保存了正确的删除器,和shared_ptr的模板参数Base无关——这是一种基于控制块的运行时多态实现,绕过了基类非虚析构的限制。

关于内存泄漏的隐藏

shared_ptr这种“侥幸正确”的行为属于未定义行为的特例生效,C++标准并不保证这种行为在所有编译器或环境下都成立。换成unique_ptr后,编译期绑定的删除器直接暴露了非虚析构的问题,反而能帮你及时发现代码隐患。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 03:42:32