虚拟析构函数和删除具有多重继承的对象...它是如何工作的? [英] Virtual destructors and deleting objects with multiple inheritance... How does it work?

查看:166
本文介绍了虚拟析构函数和删除具有多重继承的对象...它是如何工作的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先,我明白为什么 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;

删除a;
delete b;
}

这个输出是:


〜AB

〜B < br>
〜A

〜AB

〜B

〜A


编译器如何知道如何调用 A '和 B 删除 a b 时,具体来说,如何布局 AB 的内存(特别是它的虚拟函数表),这样 A code> B 析构函数可以调用?



我的教授建议内存布局如下: / p>

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

//我不知道〜AB会去哪里...

我们都很好奇这些析构函数是如何实际布局在内存中的,以及如何调用 delete code> a 或 b 会导致正确调用所有析构函数。删除一个基础对象在单继承(因为有一个单一的虚拟函数表使用)是有意义的,但显然我不能正确地理解事情,因为我不能理解单个继承版本并应用它

解决方案



p>

实际上,编译器将隐式调用插入到〜A()〜B()转换为〜AB()。该机制与单继承完全相同,除了有多个基本析构函数供编译器调用。



我认为你的图中的主要混乱来源是虚拟析构函数的多个单独的vtable条目。在实践中,将有一个条目指向〜A()〜B() code>〜AB()用于 A B 和<$ c例如,如果我使用 gcc 来编译代码,那么$ c> AB() $ c>并检查程序集,我看到〜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

这会调用〜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

指的是类的完全对象析构函数。对于 A ,这指向〜A()等。


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 multiple inheritance and the reason behind why this works. This question came up in one of my university classes, and no one (including the professor) was sure why this worked:

#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;
}

The output for this is:

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

How does the compiler know how to call A's and B's destructor when deleting a or b? Specifically, how is the memory for AB laid out (particularly it's virtual function table), such that the A and B destructors can be called?

My professor was suggesting that memory would be laid out (something) like this:

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

// I have no idea where ~AB would go...

We're all curious how these destructors are actually laid out in memory and how calling delete on either a or b results in all the destructors being properly called. It makes sense that deleting a base object works in single inheritance (because there's a single virtual function table to work with), but apparently I'm not understanding things correctly because I can't take my understanding of the single inheritance version and apply it to this multiple inheritance example.

So how does this work?

解决方案

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.

这篇关于虚拟析构函数和删除具有多重继承的对象...它是如何工作的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆