在hairetz的一篇转的博文中(下面的链接)提到虚函数表解析,写的非常不过,不过那篇没有讨论虚继承情况,我自己试了虚继承情况。
http://blog.csdn.net/hairetz/archive/2009/04/29/4137000.aspx
下面是在VS08环境中测试的。
class Base
{
public:
Base():a(10){}
virtual void f(){cout<<"Base::f"<<endl;}
virtual void g(){cout<<"Base::g"<<endl;};
int a;
};
class Derived:virtual public Base
{
public:
virtual void f1() { cout << "Derived::f1" << endl; }
virtual void g1() { cout << "Derived::g1" << endl; }
virtual void f() { cout << "Derived::f" << endl; }
};
int main()
{
typedef void (*Fun)();
Derived d;
Fun pFun;
pFun=(Fun)*((int*)*((int*)(&d)+0)+0);
pFun();
pFun=(Fun)*((int*)*((int*)(&d)+0)+1);
pFun();
cout<<*((int*)*((int*)(&d)+0)+2)<<endl;
pFun=(Fun)*((int*)*((int*)(&d)+2)+0);
pFun();
pFun=(Fun)*((int*)*((int*)(&d)+2)+1);
pFun();
cout<<*((int*)*((int*)(&d)+2)+2)<<endl;
cout<<sizeof(Derived)<<endl;
cout<<*((int*)(&d)+3)<<endl;//虚基类中的a成员变量
return 0;
}
输出
Derived::f1
Derived::g1
0
Derived::f
Base::g
0
16
10
从输出的大小看继承类大小16,包括基类的8字节(a和vptr),继承类自己的vptr,还有指向基类的对象的指针。所以是16字节。
注意这里继承类有自己的虚函数,不同于普通继承,普通的public继承会把子类的vtable中的函数指针合并到父类的vtable中,虚继承中会子类有自己的vtable和vptr。另外,子类会有指向父类对象subobject的指针,这个是我在 深入探索c++对象模型 中看到的。不同的编译器对这个实现也不一样,g++下运行结果也不相同。所以这个不用再深入了。
下面分析上面运行结果。
(int*)(&d)+0是d的第一个字节,(int*)*((int*)(&d)+0)+0 是虚函数表的第一项,后面+1表示虚函数表的第二项,由于子类只有两个虚函数,所以vtable中只有两个,后面是0表示结束的。从输出看输出的是子类的函数,所以在虚继承中子类在前,这个普通继承是不一样的。
int*)(&d)+2是d的第3个字节,(int*)*((int*)(&d)+2)+0是vtable的第一项,后面+1是第二项,从输出看这个确实是基类的vtable,而且由于子类覆盖了f函数,所以输出被子类的替换了,同样vtable最后一项是0表结束。
如果理解基本的虚函数表,这里应该不难理解。这里问题主要是虚继承时子类对象的结构,从 深入探索c++对象模型 中对照,VS基本还是和书上讲的实现方法是一样的,gcc不一样。至于这里第二个指针指向基类的指针 ,我试着验证地址老是不对,只好放弃。
下面是gcc下的结果
#include <iostream>
using namespace std;
class Base
{
virtual void f(){cout<<"Base::f"<<endl;}
virtual void g(){cout<<"Base::g"<<endl;};
int a;
};
class Derived:virtual public Base
{
public:
virtual void f1() { cout << "Derived::f1" << endl; }
virtual void g1() { cout << "Derived::g1" << endl; }
virtual void f() { cout << "Derived::f" << endl; }
};
int main()
{
Derived d;
typedef void (*Fun)();
Fun pFun;
cout<<sizeof(Derived)<<endl;
pFun=(Fun)*((int*)*((int*)(&d))+1);
pFun();
pFun=(Fun)*((int*)*((int*)(&d)+1)+1);
pFun();
return 0;
}
结果
12
Derived::g1
Base::g
同样gcc下继承类在前面,基类在后面,但只有3*4的大小,看起来没有指向基类的指针。如果基类没有成员a的话,继承类只是4字节,子类和基类的vtable会合并到一起。
关于菱形结构的虚继承也不研究了,VC和g++的实现不一样,我觉得就没必要再深入了。
这些在求对象大小sizeof的时候也会要判断各种情况,由此可见不同编译器还是不一样。