当 dtor 不是虚拟的时,为什么 shared_ptr 和 unique_ptr 有不同的行为?

Why do shared_ptr and unique_ptr have different behavior when dtors are not virtual?

提问人:josh chatham 提问时间:11/17/2023 最后编辑:Jean-Baptiste Yunèsjosh chatham 更新时间:11/18/2023 访问量:73

问:

所以,我知道不使多态类析构函数虚拟会导致未定义的行为,而正确的解决方法是使它们成为虚拟的。话虽如此,为什么shared_ptr在破坏时“拯救”你?(我在 RHEL 8 上使用 gcc/g++)

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,期望相同的行为,但得到了不同的行为。

C++ 内存泄漏未 定义行为 虚拟继承

评论

2赞 Jarod42 11/17/2023
std::shared_ptr 有“动态”删除器...
2赞 ChrisMM 11/17/2023
只是为了回答标题中的问题:未定义的行为是......定义。一分钱一分货。
5赞 Igor Tandetnik 11/17/2023
准确地说:创建一个 ,它存储一个指针以及一个知道如何删除指针的删除器。然后从中构造 a - 在此过程中,指针被转换为 ,但原始删除器仍然存在于共享控制块中。std::make_shared<Derrived>()shared_ptr<Derived>Derived*Derived*shared_ptr<Base>shared_ptr<Derived>Derived*Base*Derived*
1赞 Igor Tandetnik 11/17/2023
您帖子的标题具有误导性。尽管缺少虚拟析构函数,但使用的代码部分具有明确定义的行为。std::shared_ptr
2赞 Pete Becker 11/18/2023
真正的规则比问题所描述的要窄。如果程序通过指向基类型的指针删除派生类型的对象,并且基类型的析构函数不是虚拟的,则行为是未定义的。没有一般规则规定基类的析构函数必须是虚拟的;这取决于类型的使用方式。

答:

4赞 Jean-Baptiste Yunès 11/18/2023 #1

来自 cppreference。

对于unique_ptr:

如果 T 是某个基 B 的派生类,则 隐式转换为 。默认删除程序 结果将对 B 使用运算符 delete, 导致未定义的行为,除非 B 的析构函数是虚拟的。请注意,std::shared_ptr 的行为不同:std::shared_ptr 将对类型 T 使用运算符 delete,即使 B 的析构函数不是虚拟的,拥有的对象也会被正确删除。std::unique_ptr<T>std::unique_ptr<B>std::unique_ptr<B>

对于从另一个shared_ptr构造时shared_ptr(案例 8、9、10):

别名构造函数:构造一个共享的shared_ptr 初始值为 r 的所有权信息,但拥有 不相关和非托管指针 PTR。如果此shared_ptr是最后一个 该组超出范围,它将调用存储的删除程序 最初由 R 管理的对象

现在我只是好奇为什么它不同?shared_ptr以某种方式跟踪对象的动态类型吗?

unique_ptr被设计得尽可能高效。

shared_ptr显然可以共享该对象,因此您可能希望在该对象上使用不同的指针类型,然后需要跟踪原始删除程序。相反,如果删除在对象的一个或另一个shared_ptr上使用时的行为会有所不同,您会感到非常惊讶。

评论

0赞 Eljay 11/18/2023
优秀的答案!只是想补充一下:甚至可以通过 的别名构造函数将对象的一部分(如对象的成员变量)设置为shared_ptr。拥有存储删除程序的另一个重要原因。别名构造函数不是这样做的东西(也没有语义意义)。shared_ptrshared_ptrunique_ptr
0赞 Red.Wave 11/18/2023
是缺陷。它必须将转换限制为源和目标。我将定义一个自定义删除器模板,以及使用新的默认删除器在个人命名空间中定义一个别名。std::default_deleterstd::has_virtual_destructorunique_ptr