- // 含有虚拟继承对象的空间大小.cpp : 定义控制台应用程序的入口点。
- //
- #include "stdafx.h"
- #include <iostream>
- using namespace std;
- class A{
- public:
- protected:
- private:
- };
- class B{
- public:
- protected:
- private:
- };
- class C:public A,public B{
- public:
- protected:
- private:
- };
- class D:virtual public A
- {
- public:
- protected:
- private:
- };
- class E:virtual public A,virtual public B{
- public:
- protected:
- private:
- };
- class F{
- public:
- int a;
- static int b;
- protected:
- private:
- };
- int F::b=10;
- int _tmain(int argc, _TCHAR* argv[])
- {
- cout<<"sizeof(A)="<<sizeof(A)<<endl;
- cout<<"sizeof(B)="<<sizeof(B)<<endl;
- cout<<"sizeof(C)="<<sizeof(C)<<endl;
- cout<<"sizeof(D)="<<sizeof(D)<<endl;
- cout<<"sizeof(E)="<<sizeof(E)<<endl;
- cout<<"sizeof(F)="<<sizeof(F)<<endl;
- getchar();
- return 0;
- }
由于A是空类,编译器会安插一个char,标记它的每一个对象,因此其大小为1个字节。
B类和A类一样,也是一个字节。
类C是多重继承自A和B,其大小仍然为1。
类D是虚继承自A,编译器为该类安插一个指向父类的指针,指针大小为4,由于此类有了指针,编译器不会安插一个char了,因此其大小为4字节。
类E虚继承自A并且也虚继承自B,因此它有指向父类A的指针与父类B的指针,加起来大小为8字节。
类F含有一个静态成员变量,这个静态成员的空间不在类的实例中,而是像全局变量一样在静态存储区,被类共享,因此其大小为4字节。
虚继承就是引入了虚基类的继承。引入虚基类的目的是为了解决类继承过程中产生的二义性问题。这种二义性问题常见于具有菱形继承关系的类继承体系中。比如: 有四个类:A、B、C、D,它们之间的继承关系是:B继承A,C继承A,D继承B和C。这就形成了一个菱形的继承关系,具有这种继承关系的图叫做有向无环 图。那么类D就有两条继承路径:D-->B-->A和D-->C-->A。而类A是派生类D的两条继承路径上的公共基类,那么这个公共基类就会在派生类D的对象中产生多个基类子对象,这个时候在D中引用基类A的成员时,就会产生明显的二义性。要解决这个二义性,就必须将这个基类A设定为虚基类。
引进虚基类之后,派生类(子类)的对象中只存在一个虚基类的子对象。当一个类拥有虚基类的时候,编译系统会为这个类的对象定义一个指针成员,并让它指向虚基类的子对象。该指针被称为虚基类指针。这个概念与虚函数表指针不同。在内存中,一般情况下,虚基类子对象在派生类对象中是放置在派生类对象所占内存块的尾部,不过这是由编译器来决定的。
C++虚继承内存对象模型探讨
[cpp] view
plaincopy
- class A {
- int a;
- virtual ~A(){}
- };
- class B:virtual public A{
- virtual ~B(){}
- virtual void myfunB(){}
- };
- class C:virtual public A{
- virtual ~C(){}
- virtual void myfunC(){}
- };
- class D:public B,public C{
- virtual ~D(){}
- virtual void myfunD(){}
- };
首先,说说GCC的编译器.
它实现比较简单,不管是否虚继承,GCC都是将虚表指针在整个继承关系中共享的,不共享的是指向虚基类的指针。
以上代码中 sizeof(A)=8,sizeof(B)=12,sizeof(C)=12,sizeof(D)=16.
解释:A中int+虚表指针。B,C中由于是虚继承因此大小为A+指向虚基类的指针,B,C虽然加入了自己的虚函数,但是虚表指针是和基类共享的,因此不会有自己的虚表指针。D由于B,C都是虚继承,因此D只包含一个A的副本,于是D大小就等于A+B中的指向虚基类的指针+C中的指向虚基类的指针。
如果B,C不是虚继承,而是普通继承的话,那么A,B,C的大小都是8(没有指向虚基类的指针了),而D由于不是虚继承,因此包含两个A副本,大小为16. 注意此时虽然D的大小和虚继承一样,但是内存布局却不同。
然后,来看看VC的编译器
vc对虚表指针的处理比GCC复杂,它根据是否为虚继承来判断是否在继承关系中共享虚表指针,而对指向虚基类的指针和GCC一样是不共享,当然也不可能共享。
代码同上。
运行结果将会是sizeof(A)=8,sizeof(B)=16,sizeof(C)=16,sizeof(D)=24.
解释:A中依然是int+虚表指针。B,C中由于是虚继承因此虚表指针不共享,由于B,C加入了自己的虚函数,所以B,C分别自己维护一个虚表指针,它指向自己的虚函数。(注意:只有子类有新的虚函数时,编译器才会在子类中添加虚表指针)因此B,C大小为A+自己的虚表指针+指向虚基类的指针。D由于B,C都是虚继承,因此D只包含一个A的副本,同时D是从B,C普通继承的,而不是虚继承的,因此没有自己的虚表指针。于是D大小就等于A+B的虚表指针+C的虚表指针+B中的指向虚基类的指针+C中的指向虚基类的指针。
同样,如果去掉虚继承,结果将和GCC结果一样,A,B,C都是8,D为16,原因就是VC的编译器对于非虚继承,父类和子类是共享虚表指针的。