12

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 .

PeerPandit
  • 309
  • 1
  • 4
  • 16
  • I've idea about virtual table mechanism, But vtable have entries of other virtual functions , What about Destructors? Kindly Correct me If i'm missing or messing something? – PeerPandit Oct 13 '11 at 06:37
  • 2
    As the answers show, virtual destructors are handled just like other virtual functions, so usually using a vtable. For more information on that magic, and other ways to do it, check [this question](http://stackoverflow.com/questions/4352032/). – Björn Pollex Oct 13 '11 at 07:03

8 Answers8

11

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;
}
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • In simple words the requirement means: `this` in the constructor or destructor is always pointer to the type to which the constructor or destructor belongs. – Alok Save Oct 13 '11 at 08:33
  • @Als: Kind of, but it is important to note that it is not that the pointer is of that type, but that the pointee type changes as the object is being destroyed up the hierarchy. – David Rodríguez - dribeas Oct 13 '11 at 08:53
  • A question to your answer. As you mentioned ".. it follows the vptr of the static type of the pointer to find the vtable..". Shouldn't it be the **dynamic** type of the pointer to find the vtable? If it follows the vptr of the static type that would be the vptr of the base class, or what am I missing her? – awakened Aug 25 '19 at 17:05
4

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.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 1
    The OP asks about *magic* for virtual detructor,The answer only explains virtual functions.Apart from the fact that destructor behaves as all other virtual functions,there is more magic to it.The most derived class destructor gets called but it also automatically results in calls to Base class destructors of the class.The answer fails to answer the *real* Q that the OP is asking. – Alok Save Oct 13 '11 at 06:52
  • @Als: I realized that and so I added the needed explanation for that as well. Thanks for the comment, anyway. – Nawaz Oct 13 '11 at 06:56
  • 4
    You should also probably add to it the fact that for a destructor `The most derived class destructor gets called through dynamic dispatch but it also automatically results in calls to Base class destructors of the class` that would explain the complete behavior for destructors unlike other functions. – Alok Save Oct 13 '11 at 06:59
  • @Als: Yup. Good point. Let me add the text from your comment itself. – Nawaz Oct 13 '11 at 07:59
3

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.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
jpalecek
  • 47,058
  • 7
  • 102
  • 144
  • 1
    I agree with this except for the if(should_delete) "delete this". Real C++ destructors have no such feature anything like this, nor should they. Instead, when delete is called, they invoke the destructor, then delete that block of memory afterwards. No recursive heap freeing. – VoidStar Oct 13 '11 at 07:40
  • @VoidStar: Then you have to disagree with the C++ standard itself and its g++ implementation. See how it works in action: http://ideone.com/F9gjv – jpalecek Oct 13 '11 at 07:53
  • @jpalecek: That demo in ideone does not demonstrate anything, and I the standard dictates as part of the description of the *delete-expression* §5.3.5/6 The delete-expression will invoke the destructor (if any) for the object or the elements of the array being deleted. [...] §5.3.5/7 The delete-expression will call a deallocation function. The destructor *does not* call the deleter, but the *delete-expression* calls the *destructor* and then the *deallocator* – David Rodríguez - dribeas Oct 13 '11 at 08:02
  • BTW there is no "recursive heap freeing" in the example. – jpalecek Oct 13 '11 at 08:02
  • That for the one thing that should not be in the answer, as of the other end, you are missing just the bit where prior to entering the destructor the dynamic type of the object is set to be that of the destructor being called. But overall a good answer that tackles what most ignored (destruction) +1 – David Rodríguez - dribeas Oct 13 '11 at 08:04
  • @DavidRodríguez-dribeas: And how would the delete-expression know which deallocation function would be called? What I've described fulfills precisely your quotation from the standard. How would you otherwise explain, that `delete x` will call `operator delete` from the most derived subobject of `*x` **iff** x's destructor is virtual? – jpalecek Oct 13 '11 at 08:20
  • @jpalecek: Good point, I had never thought of that. Sadly even if I have learnt something new I cannot vote more than once... – David Rodríguez - dribeas Oct 13 '11 at 08:26
  • @DavidRodríguez-dribeas: OK. I reflected your point about setting the dynamic type to the answer. – jpalecek Oct 13 '11 at 09:33
  • I've been reading some more on this particular issue, and the solution that is implemented in gcc is actually slightly different. The compiler generates three different destructors that serve to manage the different combinations of *manages the virtual bases and relases memory*, *manages virtual bases but does not release memory* and *does not manage virtual bases*. (There are also two constructors, one that initializes virtual bases and another that doesn't). Still conceptually there is an `if` there, even if the decision is taken by external code calling different destructors – David Rodríguez - dribeas Oct 14 '11 at 16:27
  • @jpalecek when you say "And how would the delete-expression know which deallocation function would be called?", the delete-expression doesn't know that, it just calls `operator delete(void *)` with the pointer to `x`. It doesn't need to know the size of `x`, since a pointer to the allocated memory is enough for the `free`. `delete x` does not call `operator delete` from the most derived object, the call is made from the `delete x` statement. This is what the standard says, and this is the way it is implemented in VisualC++, this is why there are problems with dlls overriding `operator delete`. – Raffi Feb 22 '13 at 14:16
2

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.

sharptooth
  • 167,383
  • 100
  • 513
  • 979
1

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Except that ALL inherited destructors are called, where as only the most-derived override is called for normal functions. This requires either a bit of special handling in the vtable or hidden invocations of the super's destructor in the destructor's body. – VoidStar Oct 13 '11 at 07:22
  • @VoidStar Well, that's a pragmatic language decision. For normal virtual functions, the function can opt to call the base class function, or not. For a destructor the language specifies that this will always happen and the call to base class destructor is implicit. The underlying mechanism to implement this will typically be the same. – David Heffernan Oct 13 '11 at 08:07
1

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.

K-ballo
  • 80,396
  • 20
  • 159
  • 169
1

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.

VoidStar
  • 5,241
  • 1
  • 31
  • 45
  • +1 overall, but *There is one such offset stored in the `vtable`*: Most compilers will use a *thunk* or *trampoline function* to fix the `this` pointer. That is, the entry in the `vtable` will either point to the final overrider if `this` does not need to be offsetted, or to a *thunk* that will perform the offset and forward the call. The advantage over storing the offsets in the `vtable` is that you only pay for the offset when you need it (with the offset stored, the compiler would have to add it, even if in most cases it is 0) – David Rodríguez - dribeas Oct 13 '11 at 07:41
0

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.

Brent Arias
  • 29,277
  • 40
  • 133
  • 234