You may have heard the advice that a destructor should be declared as virtual in case you use the class as a base class. Within this article I want to show the technical background behind this advice and want to explain why you should follow this guideline.
Let’s start by implementing a base class and a derived class and test the behavior of the destructor.
class Animal { public: ~Animal() { std::cout << "dtor of Animal\n"; } }; class Bird : public Animal { public: ~Bird() { std::cout << "dtor of Bird\n"; } }; int _tmain(int argc, _TCHAR* argv[]) { Animal *pAnimal = new Animal(); delete pAnimal; std::cout << "\n"; Bird *pBird = new Bird(); delete pBird; return 0; }
The console application creates the following output:
dtor of Animal
dtor of Bird
dtor of Animal
As expected the according destructors a called. So far we don’t see the need for a virtual base class destructor.
But what happens in case the derived class is deleted by using a base class pointer. If the base class has a non-virtual destructor the result is undefined according to the C++ standard. And that’s easy to explain because in this case the compiler cannot know which of the two destructors you want to call because this may be different according to the current context of you implementation. In this case the result can be a partly destroyed object because the compiler will call the base class destructor only. Within the console application example the compiler will call the base class destructor only. But another compiler may call the derived class destructor.
int _tmain(int argc, _TCHAR* argv[]) { Animal *pAnimal = new Bird(); delete pAnimal; return 0; }
Output:
dtor of Animal
As explained it is undefined how the compiler will handle the above implementation and therefore it is absolutely necessary that we implement the destructor in a way that we have a defined behavior. This can be done by using a virtual destructor. In case your base class has a virtual destructor, the derived class will overwrite it. And now, the above example will work without any issues. Regardless whether you use a pointer to the derived class or a pointer to the base class, you can destroy the derived class object in any case because it now has only one destructor, the one of the derived class which has overwritten the one of the base class.
class Animal { public: virtual ~Animal() { std::cout << "dtor of Animal\n"; } }; class Bird : public Animal { public: ~Bird() { std::cout << "dtor of Bird\n"; } }; int _tmain(int argc, _TCHAR* argv[]) { Animal *pAnimal = new Bird(); delete pAnimal; return 0; }
Output:
dtor of Bird
dtor of Animal
If a non-virtual destructor can cause such a trouble you may come to the conclusion to always declare you destructors virtual no matter whether you want to use the class as base class or not. But unfortunately that’s not a good idea too. Virtual functions will lead to some overhead behind the scene. A virtual table pointer will be added to the class which points to the virtual table. This table is an array of function pointers. When a virtual function is called the actual functions is determined by looking up the appropriate function within this virtual table.
If you use a virtual destructor or virtual functions in general you have to deal with two main issues: The overhead of managing the virtual table pointer and the table itself and the incompatibility with other programming languages. For example if you want to pass your object pointer from C++ to a C component you may implement additional converters as there is no virtual table pointer in C and therefore the C++ object is no longer equal to the same object implemented in C.
STL types
As we now know that we should not inherit from a class with a non-virtual destructor we may ask the question: Can we inherit from STL types?
For example you may want to implement your own vector class which offers some additional functions compared to the STL vector type. In this case you may think it will be nice to inherit from the STL type:
class MyVector : public std::vector{};
As the STL container types do not have a virtual destructor this implementation may lead to the destructor issue show above. Therefore you should not use the STL types for inheritance.
Summary
If a derived class is deleted by using a base class pointer and the base class has a non-virtual destructor the result is undefined. To handle this use case you have to implement virtual destructors. But on the other hand always declaring your destructors virtual is just as wrong as never declaring them virtual. Therefore you have to carefully choose the right implementation with respect to the according use cases. In case a class is used for inheritance you should implement a virtual destructor, otherwise implement a non-virtual one.