关于c ++:了解虚拟继承类vtable和vptr创建

Understanding virtual inheritance class vtables and vptr creation

下面的代码具有多重继承性,其中每个类都有一个成员变量、一个普通函数和一个虚拟函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class basec
{
    int x;
public:
    basec()
    {
        x = 0;
    }
    void print()
    {}

    virtual void xyz()
    {}
};

class derivedc: public virtual basec
{
    int dc;
public:
    derivedc()
    {
        dc = 0;
    }
    virtual void xyzdc()
    {}
};

class  derivedd: public virtual basec
{
    int dd;
public:
    derivedd()
    {
        dd = 0;
    }
    virtual void xyzdd()
    {}
};

class child: public derivedc, public derivedd
{
    char de;
public:
    child()
    {
        de = '4';
    }
    virtual void xyzde()
    {}
};

main(int argc, char **argv)
{
    basec bobj, bobjc;    
    derivedc dcobj;
    derivedd ddobj;
    child deobj;

    std::cout <<"C" << sizeof(basec) << endl;
    std::cout <<"D" << sizeof(derivedc) << endl;
    std::cout <<"E" << sizeof(derivedd) << endl;
    std::cout <<"F" << sizeof(child) << endl;

    return(0);
}

程序输出如下:

1
2
3
4
5
main()
C 8
D 16
E 16
F 28

看到gdb中的每个对象,我看到如下:

1
2
3
4
5
6
7
8
9
10
11
(gdb) p/x bobj
$1 = {_vptr.basec = 0x8048c80, x = 0x0}

(gdb) p/x dcobj
$3 = {<basec> = {_vptr.basec = 0x8048c5c, x = 0x0}, _vptr.derivedc = 0x8048c4c, dc = 0x0}

(gdb) p/x ddobj
$4 = {<basec> = {_vptr.basec = 0x8048c1c, x = 0x0}, _vptr.derivedd = 0x8048c0c, dd = 0x0}

(gdb) p/x deobj
$5 = {<derivedc> = {<basec> = {_vptr.basec = 0x8048b90, x = 0x0}, _vptr.derivedc = 0x8048b6c, dc = 0x0}, <derivedd> = {_vptr.derivedd = 0x8048b80, dd = 0x0}, de = 0x34}

我们看到,在基类"basec"和每个实际上派生的类"derivedc"和"derived"对象中,都为其vtables添加了vptr。

问题是,为什么不具有虚拟函数的子类没有自己的vtable,为什么对象中没有自己的vptr?子类的虚函数将出现在哪个类的vtable中?


编译器可以自由地将它放在现有vtables中的一个中,就像正常的继承工作一样。虚拟继承保证您只有一次虚拟基类。

例如,权利要求7和GCC 8.2都将child::xyzde()放在derivedc的vtable中。见"儿童vtable"(Godbolt上的Clang 7和GCC 8.2)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class derivedc : public virtual basec
class derivedd : public virtual basec
class child: public derivedc, public derivedd

vtable for child:
 .quad 32
 .quad 0
 .quad typeinfo for child
 .quad derivedc::xyzdc()
 .quad child::xyzde()       <- child::xyzde() together with derivedc's methods
 .quad 16
 .quad -16
 .quad typeinfo for child
 .quad derivedd::xyzdd()
 .quad 0
 .quad -32
 .quad typeinfo for child
 .quad basec::xyz()         <- basec is only once in child

如果将child基类更改为virtual,如下所示,则会得到三个单独的表:

1
class child: public virtual derivedc, public virtual derivedd

门闩叮当声:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vtable for child:
 .quad 48
 .quad 32
 .quad 16
 .quad 0
 .quad typeinfo for child
 .quad child::xyzde()      <- New vtable for child
 .quad 0
 .quad 16
 .quad -16
 .quad typeinfo for child
 .quad derivedc::xyzdc()
 .quad 0
 .quad -32
 .quad typeinfo for child
 .quad basec::xyz()        <- basec is only once in child
 .quad 0
 .quad -16
 .quad -48
 .quad typeinfo for child
 .quad derivedd::xyzdd()

如果你移除了所有的虚拟继承,你得到的basec是预期的child的两倍(godbolt上的clang)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class derivedc : public basec
class derivedd : public basec
class child: public derivedc, public derivedd

vtable for child:
 .quad 0
 .quad typeinfo for child
 .quad basec::xyz()        <- basec from derivedc
 .quad derivedc::xyzdc()
 .quad child::xyzde()      <- child::xyzde() together with derivedc's methods
 .quad -16
 .quad typeinfo for child
 .quad basec::xyz()        <- basec from derivedd
 .quad derivedd::xyzdd()

C++ VTABLE -第3部分-虚拟继承对EDOCX1、6和EDCOX1 7的解释有一些简短的解释。