c++shared-ptr

Why std::shared_ptr calls destructors from base and derived classes, where delete calls only destructor from base class?


Why when using std::shared_ptr deallocation calls destructors from both base and derived classes when second example calls only destructor from base class?

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

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

void virtual_destructor()
{
    {
        std::cout << "--------------------" << std::endl;
        std::shared_ptr<Base> sharedA(new Derived);
    }

    std::cout << "--------------------" << std::endl;
    Base * a = new Derived;
    delete a;
}

Output:

--------------------
Derived destructor
Base destructor
--------------------
Base destructor

I was expecting the same behaviour in both cases.


Solution

  • delete a is undefined behaviour, because the class Base does not have a virtual destructor and the "complete object" of *a (more accurately: the most-derived object containing *a) is not of type Base.

    The shared pointer is created with a deduced deleter that deletes a Derived *, and thus everything is fine.

    (The effect of the deduced deleter is to say delete static_cast<Derived*>(__the_pointer)).

    If you wanted to reproduce the undefined behaviour with the shared pointer, you'd have to convert the pointer immediately:

    // THIS IS AN ERROR
    std::shared_ptr<Base> shared(static_cast<Base*>(new Derived));
    

    In some sense, it is The Right Way for the shared pointer to behave: Since you are already paying the price of the virtual lookup for the type-erased deleter and allocator, it is only fair that you don't then also have to pay for another virtual lookup of the destructor. The type-erased deleter remembers the complete type and thus incurs no further overhead.