虚拟析构函数和删除具有多重继承的对象...它是如何工作的?

Cor*_*lks 19 c++

首先,我理解为什么virtual在单继承和通过基指针删除对象方面需要析构函数.这是专门关于多重继承和背后的原因,为什么这个工程.这个问题出现在我的一个大学课程中,没有人(包括教授)确定为什么这有效:

#include <iostream>

struct A
{
    virtual ~A()
    {
        std::cout << "~A" << std::endl;
    }
    int memberA;
};

struct B
{
    virtual ~B()
    {
        std::cout << "~B" << std::endl;
    }
    int memberB;
};

struct AB : public A, public B
{
    virtual ~AB()
    {
        std::cout << "~AB" << std::endl;
    }
};

int main()
{
    AB* ab1 = new AB();
    AB* ab2 = new AB();

    A* a = ab1;
    B* b = ab2;

    delete a;
    delete b;
}
Run Code Online (Sandbox Code Playgroud)

这个输出是:

~AB
~B
~A
~AB
~B
~A

编译器如何知道如何调用A的和B删除时的析构函数ab?具体来说,如何AB布置内存(特别是它的虚函数表),以便可以调用AB析构函数?

我的教授建议记忆会像这样布置(某事):

    AB
+---------+              +----+
|  A VFT  | - - - - - -> | ~A |
+---------+              +----+
| memberA |
+---------+              +----+
|  B VFT  | - - - - - -> | ~B |
+---------+              +----+
| memberB |
+---------+

// I have no idea where ~AB would go...
Run Code Online (Sandbox Code Playgroud)

我们都好奇,如何将这些析构函数在内存中实际的布局,如何调用delete无论是在ab在所有的析构函数结果被正确地调用.删除基础对象在单一继承中工作是有道理的(因为有一个虚拟函数表可以使用),但显然我不能正确理解事物,因为我不能理解单继承版本并应用它这个多继承的例子.

那么这是如何工作的呢?

NPE*_*NPE 17

它起作用,因为标准说它有效.

在实践中,编译器插入到隐式调用~A()~B()~AB().该机制与单继承完全相同,只是有多个基本析构函数供编译器调用.

我认为图中混淆的主要原因是虚拟析构函数的多个单独的vtable条目.在实践中,会有一个条目,将指向~A(),~B()~AB()A,BAB()分别.

例如,如果我使用gcc并检查程序集编译代码,我会看到以下代码~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
Run Code Online (Sandbox Code Playgroud)

~B()接下来是这个电话~A().

这三个类的虚拟表如下所示:

; 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
Run Code Online (Sandbox Code Playgroud)

对于每个类,条目#2引用类的"完整对象析构函数".因为A,这指向~A()等.


Jos*_*eld 13

vtable条目只是指向析构函数AB.它被定义为在执行析构函数之后,然后调用基类析构函数:

在执行析构函数的主体并销毁在主体内分配的任何自动对象之后,类X的析构函数调用[...] X直接基类的析构函数和[...].

因此,当编译器看到delete a;然后看到析构函数A是虚拟的时,它aAB通过使用vtable 来查找析构函数以获取动态类型(即).这会找到~AB并执行它.这导致调用~A~B.

这不是vtable,而是说" ~AB然后~A,然后~B"; 它简单地说"呼叫~AB" 涉及呼叫~A~B.