Open In App

Virtual destruction using shared_ptr in C++

Prerequisite: shared_ptr, Virtual Destructor As we know, deleting a derived class object using a pointer to a base class that has non-virtual destructor results in undefined behavior. Thus, we make the base class destructor virtual so that the polymorphic objects are being deleted properly in the correct order (i.e. the reverse order of their creation). Similar behavior can also be achieved by using a shared_ptr without having the Base class destructor virtual.

Let us have a look at the following code:




// Program to show the order of destruction of objects using
// shared_ptr
#include <iostream>
#include <memory>
using namespace std;
 
class Base {
public:
    Base()
    {
        cout << "Constructing Base" << endl;
    }
    ~Base()
    {
        cout << "Destructing Base" << endl;
    }
};
 
class Derived : public Base {
public:
    Derived()
    {
        cout << "Constructing Derived" << endl;
    }
    ~Derived()
    {
        cout << "Destructing Derived" << endl;
    }
};
 
int main()
{
    std::shared_ptr<Base> sp{ new Derived };
 
    // make_shared can also be used to create sp.
    // std::shared_ptr<Base> sp{std::make_shared<Derived>()};
    // Use sp
}

Output
Constructing Base
Constructing Derived
Destructing Derived
Destructing Base

As shown in the above output, there is no need to make the destructor of the Base class virtual anymore, while achieving the virtual destructor behavior at the same time.

How shared_ptr achieve this magical behavior?

The shared_ptr remembers the pointer type used during construction. For example,

If you say shared_ptr{ new Derived {} },
then shared_ptr will internally store a Derived*. 
If you say shared_ptr{ new Base {} }, 
then it stores a Base*. 

When the shared_ptr is destructed, it calls delete on the stored pointer. Naturally, with non-virtual destructors, for Base* it will call Base::~Base and for Derived* it will call Derived::~Derived. Important points :

Exceptional Condition: Initializing from Base

Consider the following example: 




// Program to show exception to this behavior while using
// shared_ptr
#include <iostream>
#include <memory>
using namespace std;
 
class Base {
public:
    Base()
    {
        cout << "Constructing Base" << endl;
    }
    ~Base()
    {
        cout << "Destructing Base" << endl;
    }
};
 
class Derived : public Base {
public:
    Derived()
    {
        cout << "Constructing Derived" << endl;
    }
    ~Derived()
    {
        cout << "Destructing Derived" << endl;
    }
};
 
int main()
{
    Base* p = new Derived{};
    std::shared_ptr<Base> sp{ p };
}

Output
Constructing Base
Constructing Derived
Destructing Base

Here, if the shared_ptr is initialized from Base* (here ‘p’), then this magical behavior of smart destruction will not be achieved as this will call Base::~Base() and not Derived::~Derived(). The shared_ptr will not able to find out the exact type of the object which is being pointed to by ‘p’. So in this case, the magic does not happen. Related Articles:


Article Tags :