首先关于虚继承和普通继承的知识,我总结一下:
1.普通继承时,无论派生类是否定义新的虚函数,基类和派生类总是共享一个虚函数表,不需要另加指向虚函数的指针,派生类只是将虚函数表中的元素改成了派生类的地址而已,虚函数表还是一个,指针数量也没有增加。
2.虚继承时,若是派生类只是继承或重写基类中虚函数,则基类和派生类是共享一个虚函数表;若派生类新定义了虚函数,则需要新加一个虚指针指向新的虚函数表。
虚继承中还有一个虚基类指针的概念,我需要在子类中多维护一个指向基类的虚基类指针
普通继承
当单继承且普通继承时:每个含有虚函数的类只有一个虚函数表,所以只需要一个虚表指针即可;
当多继承且普通继承时:一个子类有几个父类则会有几个虚函数表,所以就有和父类个数相同的虚表指针来标识;
总之,普通继承时,不需要额外增加虚函数表指针。
虚继承
当虚继承时:无论是单虚继承还是多虚继承,需要有一个虚基类表来记录虚继承关系,所以此时子类需要多一个虚基类表指针;而且只需要一个即可。
当虚继承时可能出现一个类中持有多个虚函数表的情况:无论是单虚继承还是多虚继承,
如果子类没有构造函数和析构函数,且子类中的虚函数都是在父类中出现的虚函数,这个时候不需要增加任何虚表指针;只需要像多继承那个持有父类个数的虚表指针来标识即可;
如果子类中含有构造函数或者析构函数或二者都有,则在子类中只要每出现一个父类中的虚函数则需要增加一个虚函数表指针来标识此类的虚函数表;
例子:
class A { private: int a; public: A() {} ~A() {} void A1() {} void A2() {} virtual void V1() = 0; virtual void V2() = 0; }; class B { private: int b; public: void B1() {} virtual void V1() = 0; void B2() {} }; class AA { private: char a[2]; public: virtual void aa() {} }; class CC : public virtual AA { private: char b[2]; char a[2]; public: virtual void bb() {} virtual void aa() {} }; int main() { cout<<sizeof(A)<<endl; cout<<sizeof(B)<<endl; cout<<sizeof(AA)<<endl; cout<<sizeof(CC)<<endl; return 0; }
在32位的编译器下,A的大小是8,int值+一个虚函数指针,4+4=8。B的大小是8,同样是int值+一个虚函数指针,4+4=8。AA的大小是8,2个char+一个虚函数指针,并对齐,4+4=8。CC的大小是20,这里是虚继承,并且子类CC中有自己的虚函数,所以需要一个虚函数指针,再加上CC中的元素,4个char元素,刚好是4,再多一个虚基类指针,4,另外再加上基类的数据2个char数据,对齐到4,还有基类中A的虚函数指针,所以是20.
那么如果我把virtual去掉,就变成了普通继承,这个时候子类中不需要维护虚基类指针,而且,可以和基类AA共用一个虚函数表,只需要一个虚函数指针,再加上数据,一共是12。
所以我们可以发现,虚继承中,除非子类不增加新的虚函数,不然是要多增加一个虚函数指针的,即基类和子类不共享虚函数表了。
另外一个例子:
class A{ char k[3]; public: virtual void aa(){}; }; class B:public virtual A{ char j[3]; public: virtual void bb(){}; }; class C:public virtual B{ char i[3]; public: virtual void cc(){}; }; int main() { cout << "sizeof(A):" << sizeof(A) << endl; /*A:虚函数指针+k B:B虚函数指针+k+A虚函数指针+j+虚基类指针 C:C虚函数指针+i+B虚函数指针+B虚基类指针+k+j+C虚基类指针+A虚函数指针*/ cout << "sizeof(B):" << sizeof(B) << endl; cout << "sizeof(C):" << sizeof(C) << endl; system("PAUSE"); return 0; }
32位编译器下,输出依次是8,20,32。