Search code examples
c++memory-leaksundefined-behaviorvirtual-inheritance

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


So, I know that not making polymorphic classes destructors virtual will lead to undefined behavior, and the correct fix to this is to make them virtual. With that being said, why does shared_ptr "save" you when destructing? (I'm using gcc/g++ on RHEL 8)

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>();
    }
}

I was working on a code base, and shared_ptr was hiding memory leaks, as soon as I made a unique_ptr of the object, it broke. Took a minute to find out why. It was a simple fix, but I could see this hiding lots of memory leaks.

Now I'm just curious why it is different? Does shared_ptr somehow track the dynamic type of the object?

I tried changing shared_ptr to unique_ptr, expected the same behavior, but got different behavior.


Solution

  • From cppreference.

    For unique_ptr:

    If T is a derived class of some base B, then std::unique_ptr<T> is implicitly convertible to std::unique_ptr<B>. The default deleter of the resulting std::unique_ptr<B> will use operator delete for B, leading to undefined behavior unless the destructor of B is virtual. Note that std::shared_ptr behaves differently: std::shared_ptr will use the operator delete for the type T and the owned object will be deleted correctly even if the destructor of B is not virtual.

    For shared_ptr when constructing a shared_ptr from another one (case 8,9,10):

    The aliasing constructor: constructs a shared_ptr which shares ownership information with the initial value of r, but holds an unrelated and unmanaged pointer ptr. If this shared_ptr is the last of the group to go out of scope, it will call the stored deleter for the object originally managed by r

    Now I'm just curious why it is different? Does shared_ptr somehow track the dynamic type of the object?

    unique_ptr was designed to be efficient as possible.

    shared_ptr can (obviously) share the object thus you may want different pointer type on that object, and then need to track the original deleter. On the contrary you would be very surprised if the deletion would behave differently when used on one or another shared_ptr to the object.