R@M3$H.NBlog

Virtual Destructor

08 August, 2013 - 4 min read

If the destructor in the base class is not made virtual, then an object that might have been declared of type base class and instance of child class would simply call the base class destructor without calling the derived class destructor.

Hence, by making the destructor in the base class virtual, we ensure that the derived class destructor gets called before the base class destructor.

class a
{
public:
a(){printf("\nBase Constructor\n");}
~a(){printf("\nBase Destructor\n");}
};

class b : public a
{
public:
b(){printf("\nDerived Constructor\n");}
~b(){printf("\nDerived Destructor\n");}
};
int main()
{
a* obj=new b;
delete obj;
return 0;
}

Output:
Base Constructor
Derived Constructor
Base Destructor

By Changing
~a(){printf("\nBase Destructor\n");}
to
virtual ~a(){printf("\nBase Destructor\n");}

Output:
Base Constructor
Derived Constructor
Derived Destructor
Base Destructor.

 

Virtual destructors:

The explicit destroying of object with the use of delete operator to a base class pointer to the object is performed by the destructor of the base-class is invoked on that object.

The above process can be simplified by declaring a virtual base class destructor.
All the derived class destructors are made virtual in spite of having the same name as the base class destructor. In case the object in the hierarchy is destroyed explicitly by using delete operator to the base class pointer to a derived object, the appropriate destructor will be invoked.

Virtual constructor:

A constructor of a class can not be virtual and if causes a syntax error.

Disclaimer: The standard does not mandate any particular way of performing this operations, it only mandates what the behavior of the operations at a higher level are. These are implementation details that are common to various implementations, but not mandated by the standard.

How does the compiler dispatch to the final overrider?

The first answer is the simple one, the same dynamic dispatch mechanism that is used for othervirtual functions is used for destructors. To refresh it, each object stores a pointer (vptr) to each of its vtables (in the event of multiple inheritance there can be more than one), when the compiler sees a call to any virtual function, it follows the vptr of the static type of the pointer to find thevtable and then uses the pointer in that table to forward the call. In most cases the call can be directly dispatched, in others (multiple inheritance) it calls some intermediate code (thunk) that fixes the thispointer to refer to the type of the final overrider for that function.

How does the compiler then call the base destructors?

The process of destructing an object takes more operations than those you write inside the body of the destructor. When the compiler generates the code for the destructor, it adds extra code both before and after the user defined code.

Before the first line of a user defined destructor is called, the compiler injects code that will make the type of the object be that of the destructor being called. That is, right before ~derived is entered, the compiler adds code that will modify the vptr to refer to the vtable of derived, so that effectively, the runtime type of the object becomes derived (*).

After the last line of your user defined code, the compiler injects calls to the member destructors as well as base destructor(s). This is performed disabling dynamic dispatch, which means that it will no longer come all the way down to the just executed destructor. It is the equivalent of adding this->~mybase(); for each base of the object (in reverse order of declaration of the bases) at the end of the destructor.

With virtual inheritance, things get a bit more complex, but overall they follow this pattern.

EDIT (forgot the (*)): (*) The standard mandates in §12/3:

When a virtual function is called directly or indirectly from a constructor (including from the mem-initializer for a data member) or from a destructor, and the object to which the call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructor’s own class or in one of its bases, but not a function overriding it in a class derived from the con- structor or destructor’s class, or overriding it in one of the other base classes of the most derived object.

That requirement implies that the runtime type of the object is that of the class being constructed/destructed at this time, even if the original object that is being constructed/destructed is of a derived type. A simple test to verify this implementation can be:

struct base {
   virtual ~base() { f(); }
   virtual void f() { std::cout << "base"; }
};
struct derived : base {
   void f() { std::cout << "derived"; }
};
int main() {
   base * p = new derived;
   delete p;
}

END