Virtual destructors and deleting objects with multiple inheritance… How does it work?

后端 未结 4 1706
既然无缘
既然无缘 2020-12-14 07:45

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

相关标签:
4条回答
  • 2020-12-14 08:07

    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.

    0 讨论(0)
  • 2020-12-14 08:07

    (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.

    0 讨论(0)
  • 2020-12-14 08:15

    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.

    0 讨论(0)
  • 2020-12-14 08:24

    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.

    0 讨论(0)
提交回复
热议问题