PeerPandit
PeerPandit

Reputation: 309

How Does Virtual Destructor work in C++

I will type an example :

class A
{
public:
virtual ~A(){}
};

class B: public A
{
public:
~B()
{
}

};



int main(void)
{
A * a =  new B;
delete a;
return 0;
}

Now in Above Example , destructors will be called recursively bottom to up . My Question is how Compiler do this MAGIC .

Upvotes: 12

Views: 10752

Answers (8)

There are two different pieces of magic in your question. The first one is how does the compiler call the final overrider for the destructor and the second one is how does it then call all the other destructors in order.

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 other virtual 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 the vtable 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 this pointer 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;
}

Upvotes: 11

jpalecek
jpalecek

Reputation: 47770

A suitable implementation of (virtual) destructors the compiler might use would be (in pseudocode)

class Base {
...
  virtual void __destruct(bool should_delete);
...
};

void Base::__destruct(bool should_delete)
{
  this->__vptr = &Base::vtable; // Base is now the most derived subobject

  ... your destructor code ...

  members::__destruct(false); // if any, in the reverse order of declaration
  base_classes::__destruct(false); // if any
  if(should_delete)
    operator delete(this);  // this would call operator delete defined here, or inherited
}

This function gets defined even if you didn't define a destructor. Your code would just be empty in that case.

Now all derived classes would override (automatically) this virtual function:

class Der : public Base {
...
  virtual void __destruct(bool should_delete);
...
};

void Der::__destruct(bool should_delete)
{
  this->__vptr = &Der::vtable;

  ... your destructor code ...

  members::__destruct(false);
  Base::__destruct(false);
  if(should_delete)
    operator delete(this);
}

A call delete x, where x is of pointer to class type, would be translated as

x->__destruct(true);

and any other destructor call (implicit due to variable going out of scope, explicit x.~T()) would be

x.__destruct(false);

This results in

  • the most derived destructor always being called (for virtual destructors)
  • operator delete from the most derived object being called
  • all members' and base classes' destructors being called.

HTH. This should be understandable if you understand virtual functions.

Upvotes: 3

Sarfaraz Nawaz
Sarfaraz Nawaz

Reputation: 361662

A virtual destructor is treated in the same way as any other virtual function. I note that you've correctly maked the base class's destructor as virtual. As such, it is no way different than any other virtual function, as far as dynamic dispatch is concerned. The most derived class destructor gets called through dynamic dispatch but it also automatically results in calls to Base class destructors of the class1.

Most compiler implements this feature using vtable and vptr, though the language specification does not mandate it. There can be a compiler which does this differently, without using vtable and vptr.

Anyway, as it true for most compilers, it is worth knowing what vtable is. So vtable is a table contains pointers of all virtual functions the class defines, and the compiler adds vptr to the class as hidden pointer which points to the correct vtable, so the compiler uses correct index, calculated at compile-time, to the vtable so as to dispatch the correct virtual function at runtime.

1. The italicized text is taken from @Als's comment. Thanks to him. It makes things more clear.

Upvotes: 4

VoidStar
VoidStar

Reputation: 5421

Unlike other virtual functions, when you override a virtual destructor, your object's virtual destructor is called in addition to any inherited virtual destructors.

Technically this can be achieved by whatever means the compiler chooses, but almost all compilers achieve this via static memory called a vtable, which permits polymorphism on functions and destructors. For each class in your source code, a static constant vtable is generated for it at compile time. When an object of type T is constructed at runtime, the object's memory is initialized with a hidden vtable pointer which points to the T's vtable in ROM. Inside the vtable is a list of member function pointers and a list of destructor function pointers. When a variable of any type that has a vtable goes out of scope or is deleted with delete or delete[], all of the destructor pointers in vtable the object points to are all invoked. (Some compilers choose to only store the most derived destructor pointer in the table, and then include a hidden invocation of the superclass's destructor in the body of every virtual destructor if one exists. This results in equivalent behavior.)

Additional magic is needed for virtual and nonvirtual multiple inheritance. Assume I am deleting a pointer p, where p is of the type of a base-class. We need to invoke the destructor of the sub-classes with this=p. But using multiple inheritance, p and the start of the derived object may not be the same! There is a fixed offset which must be applied. There is one such offset stored in the vtable for each class that is inherited, as well as a set of inherited offsets.

Upvotes: 1

Brent Arias
Brent Arias

Reputation: 30195

When you have a pointer to an object, it points to a block of memory that has both the data for that object and a 'vtable pointer.' In microsoft compilers, the vtable pointer is the first piece of data in the object. In Borland compilers, it is the last. Either way, it points to a vtable that represents a list of function vectors corresponding to the virtual methods that can be invoked for that object/class. The virtual destructor is just another vector in that list of function pointer vectors.

Upvotes: 0

K-ballo
K-ballo

Reputation: 81379

A virtual destructor has an entry in the virtual table just as other virtual functions do. When the destructor is invoked -either manually or automatically from a call to delete- the most derived version is invoked. A destructor also automatically calls the destructor for its base classes, so that in combination with the virtual dispatch is what causes the magic.

Upvotes: 1

David Heffernan
David Heffernan

Reputation: 613352

It's up to the compiler how to implement it and typically it's done with the same mechanism as other virtual methods. In other words there's nothing special about destructors that requires a virtual method dispatch mechanism that is distinct from that used by normal methods.

Upvotes: 1

sharptooth
sharptooth

Reputation: 170509

As usual with virtual functions there will be some implementation mechanism (like a vtable pointer) that will let the compiler find which destructor to run first depending on the type of the object. Once the most derived class destructor is run it will in turn run the base class destructor and so on.

Upvotes: 2

Related Questions