菱形继承下的虚函数表指针

本文基于VS2012编译器,不同的编译器结果可能有较大出入

来看这样一段代码

class A
{ };
class C:virtual public A
{ };
class B:virtual public A
{ };
class D:public C,public B
{ };
int main ()
{
	cout<<sizeof(A)<<endl;
	cout<<sizeof(C)<<endl;
	cout<<sizeof(B)<<endl;
	cout<<sizeof(D)<<endl;
}

最简单的菱形继承,C和B中因为virtual继承了A,所以内含一个虚基类指针指向A,A由编译器自动填充一个char

运行结果

D继承了C和B,拥有C和B包含的成员变量,两个虚基类指针,而且在当前环境下,A被布局在D的尾部,详情如下

1>  class A    size(1):
1>      +---
1>      +---

1>  class C    size(4):
1>      +---
1>   0    | {vbptr}
1>      +---
1>      +--- (virtual base A)
1>      +---
1>  

1>  class B    size(4):
1>      +---
1>   0    | {vbptr}
1>      +---
1>      +--- (virtual base A)
1>      +---
1>  

1>  class D    size(8):
1>      +---
1>      | +--- (base class C)
1>   0    | | {vbptr}
1>      | +---
1>      | +--- (base class B)
1>   4    | | {vbptr}
1>      | +---
1>      +---
1>      +--- (virtual base A)
1>      +---
1>  

事实和我们的预测符合,接下来引入虚函数

class A
{ };
class C:virtual public A
{
public:
	virtual void fun(){ }
};
class B:virtual public A
{
public:
	virtual void fun(){ }
};
class D:public C,public B
{
public:
	virtual void fun(){ }
};
int main ()
{
	cout<<sizeof(A)<<endl;
	cout<<sizeof(C)<<endl;
	cout<<sizeof(B)<<endl;
	cout<<sizeof(D)<<endl;
}

现在C里除了虚基类指针还有一个虚函数表指针,B同理,D继承来自两个具有虚函数的类,所以D里也会有两个虚函数表指针,

加上B和C的虚基类指针,一共16

结果如下

1>  class A    size(1):
1>      +---
1>      +---
1>  


1>  
1>  class C    size(8):
1>      +---
1>   0    | {vfptr}
1>   4    | {vbptr}
1>      +---
1>      +--- (virtual base A)
1>      +---
1>  
1
1>  
1>  class B    size(8):
1>      +---
1>   0    | {vfptr}
1>   4    | {vbptr}
1>      +---
1>      +--- (virtual base A)
1>      +---
1>  

1>  
1>  class D    size(16):
1>      +---
1>      | +--- (base class C)
1>   0    | | {vfptr}
1>   4    | | {vbptr}
1>      | +---
1>      | +--- (base class B)
1>   8    | | {vfptr}
1>  12    | | {vbptr}
1>      | +---
1>      +---
1>      +--- (virtual base A)
1>      +---
 

可是如果把上边A类的代码中加入一个虚函数

class A
{
public:
	virtual void fun() { }
};

会产生意想不到的结果

D的大小居然变成了12,百思不得其解,只能求助于编译器

1>  class A    size(4):
1>      +---
1>   0    | {vfptr}
1>      +---
1>  

1>  
1>  class C    size(8):
1>      +---
1>   0    | {vbptr}
1>      +---
1>      +--- (virtual base A)
1>   4    | {vfptr}
1>      +---
1>  

1>  
1>  class B    size(8):
1>      +---
1>   0    | {vbptr}
1>      +---
1>      +--- (virtual base A)
1>   4    | {vfptr}
1>      +---
1>  

1>  
1>  class D    size(12):
1>      +---
1>      | +--- (base class C)
1>   0    | | {vbptr}
1>      | +---
1>      | +--- (base class B)
1>   4    | | {vbptr}
1>      | +---
1>      +---
1>      +--- (virtual base A)
1>   8    | {vfptr}
1>      +---
1>  

忽视了很重要的一点,当派生类在执行自己的构造函数的时候,修改的是第一个继承的基类的虚函数表指针,也就是说B和C在继承A的时候,使用的指针是原本属于A的,是A的成员变量,B和C并没有生成自己的虚函数表指针。又因为虚继承的原因,保证了虚基类在派生类中只有唯一一个实例,所以到了D中,B和C的虚函数表就被诡异的合成了一个,而且其中已经被D改写为指向自己的虚函数表。bravo~

还有一点,在多重继承下,所有基类的虚函数指针虽然都会被保留到派生类中,但是他们的偏移量都会被设置成第一个虚函数表指针,而且第一个虚函数表指针指向当前对象的虚函数表。

------------

又试了一下,虚拟继承的子类如果有新定义的虚函数,内部还是会有自己的虚函数表指针,也就是说虽然只有一个基类,但是子类却还是会有两个虚函数表指针,十分复杂。

引用一下Lippman在书中的话  “我的建议是,不要再一个virtual base class中声明nonstatic data members。如果这么做,你会距离复杂的深渊愈来愈近,终不可拔。”

猜你喜欢

转载自blog.csdn.net/qq_33113661/article/details/88750302