C++虚继承和普通继承同时存在时虚基类指针和虚基类表的分析

先看这个C++中虚继承产生的虚基类指针和虚基类表,虚函数产生的虚函数指针和虚函数表_孟小胖_H的博客-CSDN博客_虚基类指针和虚函数指针

众所周知,C++虚继承时会保存父类的父类的一个副本,这是用虚基类指针和虚基类表实现的,但是虚继承和普通继承同时存在时情况如何就不是很清楚了。

比如下面的例子大家很熟悉

一、

Son1,Son2虚继承Base,Grandson普通继承Son1,Son2

可以看到Grandson中有Son1的虚基类指针vbptr和Son2的虚基类指针vbptr,它们通过各自的虚基类表的偏移量共同指向了Base类,所以当Grandson无论通过Son1还是Son2访问Base类时都是访问同一个Base,这就避免了产生两个Base类的麻烦

class Base
{
public:
	int a;
};

class Son1 :virtual public Base
{
public:
	int a;
};

class Son2 :virtual public Base
{
public:
	int a;
};


class Grandson :public Son1, public Son2
{
public:
	int a;
};
1>class Grandson	size(24):
1>	+---
1> 0	| +--- (base class Son1)   
1> 0	| | {vbptr}
1> 4	| | a
1>	| +---
1> 8	| +--- (base class Son2)
1> 8	| | {vbptr}
1>12	| | a
1>	| +---
1>16	| a
1>	+---
1>	+--- (virtual base Base)
1>20	| a
1>	+---
1>Grandson::$vbtable@Son1@:
1> 0	| 0
1> 1	| 20 (Grandsond(Son1+0)Base)
1>Grandson::$vbtable@Son2@:
1> 0	| 0
1> 1	| 12 (Grandsond(Son2+0)Base)

二、

当Grandson都虚继承Son1,Son2时不难猜想Grandson也会产生一个vbptr指针

class Base
{
public:
	int a;
};

class Son1 :virtual public Base
{
public:
	int a;
};

class Son2 :virtual public Base
{
public:
	int a;
};


class Grandson :virtual public Son1, virtual public Son2
{
public:
	int a;
};
1>class Grandson	size(28):
1>	+---
1> 0	| {vbptr}
1> 4	| a
1>	+---
1>	+--- (virtual base Base)
1> 8	| a
1>	+---
1>	+--- (virtual base Son1)
1>12	| {vbptr}
1>16	| a
1>	+---
1>	+--- (virtual base Son2)
1>20	| {vbptr}
1>24	| a
1>	+---
1>Grandson::$vbtable@Grandson@:
1> 0	| 0
1> 1	| 8 (Grandsond(Grandson+0)Base)
1> 2	| 12 (Grandsond(Grandson+0)Son1)
1> 3	| 20 (Grandsond(Grandson+0)Son2)
1>Grandson::$vbtable@Son1@:
1> 0	| 0
1> 1	| -4 (Grandsond(Son1+0)Base)
1>Grandson::$vbtable@Son2@:
1> 0	| 0
1> 1	| -12 (Grandsond(Son2+0)Base)

Grandson的确产生了一个虚指针vbptr指向自己的虚基类表,由于虚继承了Son1,Son2,那么它的虚基类表通过偏移量指向了Base,Son1,Son2

三、

那么,如果Grandson虚继承Son1,普通继承Son2呢?情况会是怎么样

class Base
{
public:
	int a;
};

class Son1 :virtual public Base
{
public:
	int a;
};

class Son2 :virtual public Base
{
public:
	int a;
};


class Grandson :virtual public Son1,  public Son2
{
public:
	int a;
};
1>class Grandson	size(24):
1>	+---
1> 0	| +--- (base class Son2)
1> 0	| | {vbptr}
1> 4	| | a
1>	| +---
1> 8	| a
1>	+---
1>	+--- (virtual base Base)
1>12	| a
1>	+---
1>	+--- (virtual base Son1)
1>16	| {vbptr}
1>20	| a
1>	+---
1>Grandson::$vbtable@Son2@:
1> 0	| 0
1> 1	| 12 (Grandsond(Son2+0)Base)
1> 2	| 16 (Grandsond(Grandson+0)Son1)
1>Grandson::$vbtable@Son1@:
1> 0	| 0
1> 1	| -4 (Grandsond(Son1+0)Base)

可以神奇地发现,Grandson自己没有虚基类指针,但Son2的虚基类表竟然存放了指向Son1和Base的偏移量

四、

根据上面的例子可以得知

1.虚继承一定会产生有虚基类表通过偏移量去指向那个被虚继承的类

2.如果没有全部虚继承,那么会找一个普通继承但包含虚基类指针的类来做这个上面的事,因为这样能避免产生创建虚基类指针的开销(4个字节)

下面这个例子复杂一点

class Base
{
public:
	int a;
};

class Son1 :virtual public Base
{
public:
	int a;
};

class Son2 :virtual public Base
{
public:
	int a;
};

class Son3 :public Son1
{
public:
	int a;
};

class Son4 :public Son2
{
public:
	int a;
};

class Grandson :virtual public Son3,  public Son4
{
public:
	int a;
};
1>class Grandson	size(32):
1>	+---
1> 0	| +--- (base class Son4)
1> 0	| | +--- (base class Son2)
1> 0	| | | {vbptr}
1> 4	| | | a
1>	| | +---
1> 8	| | a
1>	| +---
1>12	| a
1>	+---
1>	+--- (virtual base Base)
1>16	| a
1>	+---
1>	+--- (virtual base Son3)
1>20	| +--- (base class Son1)
1>20	| | {vbptr}
1>24	| | a
1>	| +---
1>28	| a
1>	+---
1>Grandson::$vbtable@Son4@:
1> 0	| 0
1> 1	| 16 (Grandsond(Son2+0)Base)
1> 2	| 20 (Grandsond(Grandson+0)Son3)
1>Grandson::$vbtable@Son3@:
1> 0	| 0
1> 1	| -4 (Grandsond(Son1+0)Base)

 可以看到Grandson虚继承了Son3,普通继承了Son4。既然Grandson虚继承了Son3,那么就得有一个虚基类表来存放指向Son3的偏移量,刚好Son4是普通继承,而Son4继承的Son2有一个虚继承指针,所以由Son4来承担了这个任务

将Son2前面的virtual去掉看看变化

class Base
{
public:
	int a;
};

class Son1 :virtual public Base
{
public:
	int a;
};

class Son2 :public Base
{
public:
	int a;
};

class Son3 :public Son1
{
public:
	int a;
};

class Son4 :public Son2
{
public:
	int a;
};

class Grandson :virtual public Son3,  public Son4
{
public:
	int a;
};
1>class Grandson	size(36):
1>	+---
1> 0	| +--- (base class Son4)
1> 0	| | +--- (base class Son2)
1> 0	| | | +--- (base class Base)
1> 0	| | | | a
1>	| | | +---
1> 4	| | | a
1>	| | +---
1> 8	| | a
1>	| +---
1>12	| {vbptr}
1>16	| a
1>	+---
1>	+--- (virtual base Base)
1>20	| a
1>	+---
1>	+--- (virtual base Son3)
1>24	| +--- (base class Son1)
1>24	| | {vbptr}
1>28	| | a
1>	| +---
1>32	| a
1>	+---
1>Grandson::$vbtable@Grandson@:
1> 0	| -12
1> 1	| 8 (Grandsond(Grandson+12)Base)
1> 2	| 12 (Grandsond(Grandson+12)Son3)
1>Grandson::$vbtable@Son3@:
1> 0	| 0
1> 1	| -4 (Grandsond(Son1+0)Base)

 可以看到Grandson多了一个自己的虚基类指针和虚基类表,这是因为普通继承的Son4没有虚基类指针了,所以没办法,系统给Grandson分配了一个虚基类指针(大小从32变为了36)来完成指向各个虚继承来的类的任务

总结:

总的来说,只要在继承时关系链中有一次虚继承,那么之后的继承都只会保存其一个副本,而如何分配虚基类表和虚基类指针就得看系统操作了,通过少虚继承一个还可以减少类的大小,不过好像没啥用。

猜你喜欢

转载自blog.csdn.net/qq_30798083/article/details/128150821