Lei Mao bio photo

Lei Mao

Machine Learning, Artificial Intelligence, Computer Science.

Twitter Facebook LinkedIn GitHub   G. Scholar E-Mail RSS

Introduction

When we are implementing C++ runtime polymorphism using virtual methods, sometimes we would forget to make the destructor virtual. In this blog post, I would like to discuss the consequence of not using virtual destructor using some examples.

Examples

Non-Virtual Destructor

In the base class, sometimes we would just use the default destructor or implement a destructor that is not virtual.

// destructor.cpp
#include <iostream>

class Fruit
{
public:
    Fruit()
    {
        std::cout << "Base constructor called." << std::endl;
    }
    // Pure virtual method
    virtual int getColor() const = 0; 
    // Non-virtual destructor
    ~Fruit()
    {
        std::cout << "Base destructor called." << std::endl;
    }
};

class Apple : public Fruit
{
public:
    Apple()
    {
        std::cout << "Derived constructor called." << std::endl;
    }
    ~Apple()
    {
        std::cout << "Derived destructor called." << std::endl;
    }
    int getColor() const override
    {
        return 1;
    }
};

int main()
{
    Fruit* ptrFruit = new Apple();
    delete ptrFruit;
}

When we compile the program using GCC, the compiler would throw warning to us.

$ g++ destructor.cpp -o destructor -Wall
destructor.cpp: In function ‘int main()’:
destructor.cpp:45:12: warning: deleting object of abstract class type ‘Fruit’ which has non-virtual destructor will cause undefined behavior [-Wdelete-non-virtual-dtor]
   45 |     delete ptrFruit;
      |            ^~~~~~~~

When we execute the program, we got the following messages.

$ ./destructor 
Base constructor called.
Derived constructor called.
Base destructor called.

So obviously when we are destroying the derived Apple object via pointers, the base destructor ~Fruit was called, but the derived destructor ~Apple was never called.

Virtual Destructor

However, if we make the destructor of the base class virtual.

// virtual_destructor.cpp
#include <iostream>

class Fruit
{
public:
    Fruit()
    {
        std::cout << "Base constructor called." << std::endl;
    }
    // Pure virtual method
    virtual int getColor() const = 0; 
    // Virtual destructor
    virtual ~Fruit()
    {
        std::cout << "Base destructor called." << std::endl;
    }
};

class Apple : public Fruit
{
public:
    Apple()
    {
        std::cout << "Derived constructor called." << std::endl;
    }
    ~Apple()
    {
        std::cout << "Derived destructor called." << std::endl;
    }
    int getColor() const override
    {
        return 1;
    }
};

int main()
{
    Fruit* ptrFruit = new Apple();
    delete ptrFruit;
}

When we compile the program, GCC will not throw us warning.

$ g++ virtual_destructor.cpp -o virtual_destructor -Wall

When we execute the program, we got the following messages.

$ ./virtual_destructor 
Base constructor called.
Derived constructor called.
Derived destructor called.
Base destructor called.

The derived Apple object and the base Fruit object both have been successfully destroyed. This is because when we call delete ptrFruit. The destructor method points to ~Apple instead of ~Fruit. Therefore, ~Apple was invoked when we call delete ptrFruit. Once the execution of ~Apple is done, because of the nature of the derived destructor, the base destructor ~Fruit will be called then.

Conclusions

Don’t forget to make the destructor virtual when we are using runtime polymorphisms in C++.