本文基于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。如果这么做,你会距离复杂的深渊愈来愈近,终不可拔。”