Inheritance and Polymorphism

Virtual Destructors

A virtual destructor is a destructor declared with virtual in a base class. The purpose of a virtual destructor is to ensure that the destructors of derived classes are called when an object is deleted through a base class pointer or reference. Without a virtual destructor, the behaviour is undefined, and only the base class destructor is called.

Let's first think about what would occur if the destructor wasn't virtual. The following complete program allocates a derived object on the free store and then deletes it via a base pointer.

#include <iostream>

class Base
{
public:
    Base()
    {
        std::cout << "Base constructed\n";
    }

    // Run this destructor when the object lifetime ends
    ~Base()
    {
        std::cout << "Base destroyed\n";
    }
};

class Derived : public Base
{
public:
    Derived()
    {
        std::cout << "Derived constructed\n";
    }

    ~Derived()
    {
        std::cout << "Derived destroyed\n";
    }
};

int main()
{
    Base* ptr{ new Derived{} };
    delete ptr;
    return 0;
}

The new expression constructs a Derived, so both constructors are called and the output will be "Base constructed" and "Derived constructed". When delete ptr executes, the static type of ptr is Base*. Since the destructor of Base is not virtual, the compiler issues only one call to ~Base.

The destructor of Derived is never called, so the output will end with "Base destroyed".

Any resources (memory, file handles, locks, etc.) acquired by Derived are lost. The standard states this behaviour is undefined, so a program depending on it might crash or seem to work by chance.

The code generated when the base destructor is made virtual. The same program with an extra keyword produces the correct output.

#include <iostream>

class Base
{
public:
    Base()
    {
        std::cout << "Base constructed\n";
    }

    virtual ~Base()
    {
        std::cout << "Base destroyed\n";
    }
};

class Derived : public Base
{
public:
    Derived()
    {
        std::cout << "Derived constructed\n";
    }

    // Run this destructor when the object lifetime ends
    ~Derived()
    {
        std::cout << "Derived destroyed\n";
    }
};

int main()
{
    Base* ptr{ new Derived{} };
    delete ptr;
    return 0;
}

Construction still prints "Base constructed" then "Derived constructed". When delete ptr is reached, the virtual call mechanics look up the dynamic type of the object and begin destruction at the most derived class. First ~Derived is called and printed "Derived destroyed", then ~Base is called and printed "Base destroyed". It is the exact reverse of construction and every resource is released just once.

This rule also applies in the case where the pointer is kept in a container or passed over function boundaries. 

As long as the static type used for the delete operation is a base class, that base class must have a virtual destructor, else the derived part of the object is bypassed. A base class that is never ever going to be used polymorphically can leave its destructor non virtual, but any base class that defines an interface for derived objects should declare a virtual destructor, even if this destructor does nothing.

To sum up, the virtual destructor is the reason why the destruction side of polymorphism can be safe. It guarantees that the full set of destructors is invoked properly when deleting an object through a base interface.

Exercise: Virtual DestructorsStarter code, hints and solutionOpenClose
Guided practiceBeginner · 10–15 min

Practice: Virtual Destructors

Work through the mission, reveal hints only when needed, and compare your result with the model solution.

Your challenge

Mark the point where the program deletes through Base*, then clear the pointer and report that it is null. Predict the destructor order and explain why the virtual base destructor is required.

Mission checklist

  1. Run the starter as a baseline, then predict the change requested in Your challenge.
  2. Make that change in your editor; use the lesson-specific hints if you need help.
  3. Compile and run your answer, then compare with the worked solution and explain the result.

Starter program

This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.

#include <iostream>

class Base
{
public:
    Base()
    {
        std::cout << "Base constructed\n";
    }

    // Run this destructor when the object lifetime ends
    virtual ~Base()
    {
        std::cout << "Base destroyed\n";
    }
};

class Derived : public Base
{
public:
    Derived()
    {
        std::cout << "Derived constructed\n";
    }

    ~Derived()
    {
        std::cout << "Derived destroyed\n";
    }
};

int main()
{
    Base* ptr{ new Derived{} };
    delete ptr;
    return 0;
}

Hints

Hint 1

ptr is a Base* but addresses a Derived object. The corrected Base destructor is virtual, so deleting through ptr dispatches to destruction of the complete Derived object.

Hint 2

Construction starts with Base and then Derived; destruction reverses that order. Keep the virtual base destructor intact: a non-virtual version would make this deletion undefined, not merely omit a message.

Worked solution

The added message marks the deletion through Base*. Because Base has a virtual destructor, Derived is destroyed before Base. ptr is then cleared and checked without dereferencing the released object.

#include <iostream>

class Base
{
public:
    Base()
    {
        std::cout << "Base constructed\n";
    }

    // Run this destructor when the object lifetime ends
    virtual ~Base()
    {
        std::cout << "Base destroyed\n";
    }
};

class Derived : public Base
{
public:
    Derived()
    {
        std::cout << "Derived constructed\n";
    }

    ~Derived()
    {
        std::cout << "Derived destroyed\n";
    }
};

int main()
{
    Base* ptr{ new Derived{} };
    std::cout << "Deleting through Base*\n";
    delete ptr;
    ptr = nullptr;
    std::cout << "Pointer cleared: " << (ptr == nullptr) << '\n';
    return 0;
}

Example output

Base constructed
Derived constructed
Deleting through Base*
Derived destroyed
Base destroyed
Pointer cleared: 1

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.