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.
A shared_ptr may share ownership of an object while storing a pointer to another object. get() returns the stored pointer, not the managed pointer.
Deleting a derived class object using a pointer of base class type that has a non-virtual destructor results in undefined behavior.
The shared_ptr class template stores a pointer to a dynamically allocated object, typically with a C++ new-expression. The object pointed to is guaranteed to be deleted when the last shared_ptr pointing to it is destroyed or reset.
A C++ class containing virtual member functions has a non-virtual destructor. Since this class has virtual member functions, it will be used as a base class. The use of non-virtual destructors in base classes is dangerous because it can cause objects to be torn down incorrectly.
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.
A missing piece to Kerrek SB's answer is how does the shared_ptr
knows the type ?
The answer is that there are 3 types involved:
shared_ptr<Base>
)And shared_ptr
does not know of the actual dynamic type, but knows which static type was passed to its constructor. It then practices type-erasure... but remembers somehow the type. An example implementation would be (without sharing):
template <typename T>
class simple_ptr_internal_interface {
public:
virtual T* get() = 0;
virtual void destruct() = 0;
}; // class simple_ptr_internal_interface
template <typename T, typename D>
class simple_ptr_internal: public simple_ptr_internal_interface {
public:
simple_ptr_internal(T* p, D d): pointer(p), deleter(std::move(d)) {}
virtual T* get() override { return pointer; }
virtual void destruct() override { deleter(pointer); }
private:
T* pointer;
D deleter;
}; // class simple_ptr_internal
template <typename T>
class simple_ptr {
template <typename U>
struct DefaultDeleter {
void operator()(T* t) { delete static_cast<U*>(t); }
};
template <typename Derived>
using DefaultInternal = simple_ptr_internal<T, DefaultDeleter<Derived>>;
public:
template <typename Derived>
simple_ptr(Derived* d): internal(new DefaultInternal<Derived>{d}) {}
~simple_ptr() { this->destruct(); }
private:
void destruct() { internal->destruct(); }
simple_ptr_internal_interface* internal;
}; // class simple_ptr
Note that thanks to this mechanism, shared_ptr<void>
is actually meaningful and can be used to carry any data an properly dispose of it.
Note also that there is a penalty involved with this semantics: the need for indirection required for the type-erasure of the deleter
attribute.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With