Virtual Destructor in C++



A virtual destructor is a destructor declared within the base class with the virtual keyword. When deleting a derived class object using a pointer to a base class, the base class should be defined with a virtual destructor.

Virtual destructors help prevent the problem of memory leaks and undefined behavior. If a virtual destructor is not used, then only the base class destructor is called, and the resources of derived class is not cleaned up.

The Syntax for declaring a virtual destructor is given below −

class Base {
   public:
   virtual ~Base() {
      // base class destructor
    }
};

Example

The following example demonstrates how to use a virtual destructor. The virtual destructor calls the destructor of both classes i.e. base and child class

#include <iostream>
using namespace std;

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

	virtual ~Base()
	{
		cout << "Base Destructor\n";
	}
};

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

	~Derived()
	{
		cout << "Derived Destructor\n";
	}
};

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

The output of the above code is given below −

Base Constructor
Derived Constructor
Derived Destructor
Base Destructor

Why We Need Virtual Destructors?

Given below are the reasons why virtual destructors are needed −

  • When you do not use a virtual base class destructor while deleting a derived class object, it results in undefined behavior.
  • Virtual destructors make sure to delete the object of derived class too while deleting the base class object.
  • It avoids memory leak.
  • Virtual destructors maintain the order of execution of destructors i.e., first derived class, then base class.

Example: Non-Virtual Destructor

In the following example, the virtual destructor is not used. In the output, it can be observed that only the base class destructor has been called and the destructor of derived class is skipped.

#include <iostream>
using namespace std;

class Base {
public:
     // Non-virtual destructor
	~Base() {
		cout << "Base class destructor called.\n";    
	}
};

class Derived : public Base {
public:
	~Derived() {
		cout << "Derived class destructor also called.\n";
	}
};

int main() {
	Base* ptr = new Derived();
     // Only Base destructor will be called
	delete ptr;  
	return 0;
}

The output of the above code is given below −

Base class destructor called.

Example: Virtual Destructor

In this example, we have used a virtual destructor on base class Base. In the output, it can be observed that the destructor of both base and derived class is called and it maintains the reverse order of deletion.

#include <iostream>
using namespace std;

class Base {
public:
     // Virtual destructor
	virtual ~Base() {
		cout << "Base class destructor called.\n";    
	}
};

class Derived : public Base {
public:
	~Derived() {
		cout << "Derived class destructor also called.\n";
	}
};

int main() {
	Base* ptr = new Derived();
     // Both Base class and derived class destructor will be called
	delete ptr;  
	return 0;
}

The output of the above code is given below −

Derived class destructor also called.
Base class destructor called.

When to Use Virtual Destructors?

You can use a virtual destructor in the following scenarios.

  • If a virtual function is used, then use virtual destructor. Since, virtual functions are used in polymorphism to override base class. Using a virtual destructor can free up memory of both, base and derived class.
  • In inheritance, make the destructor of base class a virtual destructor as the destructor of derived class is skipped and only the base class destructor is called, it may cause a memory leak.
  • If abstract base or pure virtual function is used, then use virtual destructor on base class.

Virtual Destructor Table (Vtable)

In C++, virtual table(vtable) is a lookup table which is used by the compiler to implement virtual functions or virtual destructors. For each class that has at least one virtual function or virtual destructor, the compiler will create a vtable for that class.

A vtable has pointers to all virtual functions and the virtual destructor of a class. In a class, each object has a hidden pointer known as vptr or virtual pointer. This vptr points to the vtable of the class, which is then used to call the respective destructor. Here is a diagram representation of the vtable for the given code −

Virtual Destructor Table

Example

In this example, we have implemented the above code snippet to override the virtual functions of class A in class B and class C.

#include <iostream>
using namespace std;

class A {
public:
	virtual void f1() {
		cout << "A::f1 called" << endl;
	}
	virtual void f2() {
		cout << "A::f2 called" << endl;
	}
	virtual ~A() {
		cout << "Class A destructor called" << endl;
	}
};

class B : public A {
public:
	void f1() override {
		cout << "B::f1 called" << endl;
	}
	void f2() override {
		cout << "B::f2 called" << endl;
	}
	~B() override {
		cout << "Class B destructor called" << endl;
	}
};

class C : public A {
public:
	void f1() override {
		cout << "C::f1 called" << endl;
	}
	void f2() override {
		cout << "C::f2 called" << endl;
	}
	~C() override {
		cout << "Class C destructor called" << endl;
	}
};

int main() {
	A* ptrB = new B();
	A* ptrC = new C();

	cout << "Calling functions using pointer B:" << endl;
	ptrB->f1();
	ptrB->f2();

	cout << "Calling functions using pointer C:" << endl;
	ptrC->f1();
	ptrC->f2();

	// Calling B's destructor then A's destructor
	delete ptrB;
	// Calling C's destructor then A's destructor
	delete ptrC;

	return 0;
}

The output of the above code is given below −

Calling functions using pointer B:
B::f1 called
B::f2 called
Calling functions using pointer C:
C::f1 called
C::f2 called
Class B destructor called
Class A destructor called
Class C destructor called
Class A destructor called

Conclusion

The virtual destructor is used when working with inheritance and polymorphism. Its main purpose is to make sure that the destructors of derived classes are also called when an object is deleted through a base class pointer. This prevents memory leaks and undefined behavior.

Advertisements