First, I understand why virtual
destructors are needed in terms of single inheritance and deleting an object through a base pointer. This is specifically about
It works because the standard says that it works.
In practice, the compiler inserts implicit calls to ~A()
and ~B()
into ~AB()
. The mechanism is exactly the same as with single inheritance, except that there are multiple base destructors for the compiler to call.
I think the main source of confusion in your diagram is the multiple separate vtable entries for the virtual destructor. In practice, there will be a single entry that would point to ~A()
, ~B()
and ~AB()
for A
, B
and AB()
respectively.
For example, if I compile your code using gcc
and examine the assembly, I see the following code in ~AB()
:
LEHE0:
movq -24(%rbp), %rax
addq $16, %rax
movq %rax, %rdi
LEHB1:
call __ZN1BD2Ev
LEHE1:
movq -24(%rbp), %rax
movq %rax, %rdi
LEHB2:
call __ZN1AD2Ev
This calls ~B()
followed by ~A()
.
The virtual tables of the three classes look as follows:
; A
__ZTV1A:
.quad 0
.quad __ZTI1A
.quad __ZN1AD1Ev
.quad __ZN1AD0Ev
; B
__ZTV1B:
.quad 0
.quad __ZTI1B
.quad __ZN1BD1Ev
.quad __ZN1BD0Ev
; AB
__ZTV2AB:
.quad 0
.quad __ZTI2AB
.quad __ZN2ABD1Ev
.quad __ZN2ABD0Ev
.quad -16
.quad __ZTI2AB
.quad __ZThn16_N2ABD1Ev
.quad __ZThn16_N2ABD0Ev
For each class, entry #2 refers to the class's "complete object destructor". For A
, this points to ~A()
etc.
(I know this question is almost two years old but I couldn't resist making a point after I came across it)
Although in the title you use the question word how, you also mention why in the question post. People have given good technical answers for the how but the why seems to have gone unaddressed.
This is specifically about multiple inheritance and the reason behind why this works
This is purely guess work but sounds reasonable to me. The easiest way to look at it is that an object using multiple inheritance is composed of a number of base objects. Selectively destructing a base object will leave a hole in the composite object and that results in needless complexity when handling methods addressed to those sections of the composite object. Imagine how you would do it if you indeed used composition over multiple inheritance. So it is better to walk the object layout and destroy it as a whole.
Destructors are called in the order "most derived to most basal", and in reverse order of declaration. So ~AB
is called first, then ~B
, then ~A
, because AB
is the most derived class.
All destructors are called before the memory is actually freed. Exactly how the virtual function pointers are stored is an implementation detail, and really something you shouldn't be concerned about. A class with multiple inheritance will most likely contain two pointers to the VTABLES of the classes that it derives from, but as long as the compiler and runtime libraries together "work as we expect", it is entirely up to the compiler + runtime libraries to do what they fancy to solve these sort of issues.
The vtable entry simply points at the destructor for AB
. It is just defined that after execution of a destructor, the base class destructors are then called:
After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls [...] the destructors for
X
’s direct base classes and [...].
So when the compiler sees delete a;
and then sees that the destructor of A
is virtual, it looks the destructor up for the dynamic type of a
(which is AB
) by using the vtable. This finds ~AB
and executes it. This results in the calling of ~A
and ~B
.
It's not the vtable that says "call ~AB
, then ~A
, then ~B
"; it simply says "call ~AB
" which involves calling ~A
and ~B
.