从最简单的问题开始
class X{};
class Y:virtual public X{};
class Z:virtual public X{};
class A:public Y, public Z{};
每个类的大小是多少呢?我在vs2015上面的得到的答案是这样的(这里我使用了一个小技巧,在vs中,在项目——属性——配置属性——C/C++——命令行——其他选项中添加选项“/d1reportAllClassLayout”。再次编译时候,编译器会输出所有定义类的对象模型。)
1> class X size(1):
1> class Y size(4):
1> 0 | {vbptr}
1> +---
1> +--- (virtual base X)
1> class Z size(4):
1> +---
1> 0 | {vbptr}
1> +---
1> +--- (virtual base X)
1> class A size(8):
1> +---
1> 0 | +--- (base class Y)
1> 0 | | {vbptr}
1> | +---
1> 4 | +--- (base class Z)
1> 4 | | {vbptr}
1> | +---
1> +---
1> +--- (virtual base X)
我们可以看到这里的X的大小是1,这是被编译器安插进去的一个char。这使得class的两个不同的对象可以在内存中得到不同的地址。
我们再来看Y和Z。首先我们要明白的是实现虚继承,将要带来一些额外的负担——额外需要一个某种形式的指针。到目前为止,对于一个32位的机器来说Y、Z的大小应该为5,而不是8或者4。我们需要再考虑两点因素:内存对齐(alignment)和编译器的优化。
考虑内存对齐的原因,Y和Z的大小会增加到4的倍数,也就是相应的补齐3bytes。但是在现在大多数的编译器上做了这样的一个优化处理。因为既然我的类里面有了成员,那么就不需要为空的类按插一个char,那么这是类的大小就是4,所以也就不用3bytes去填补,所以最终Y和Z的大小就是4。关于内存对齐的问题,我不准备在本文展开。大家有兴趣可以参看我的另一篇文章关于内存对齐的总结
关于单继承问题
我们先考虑一个简单的问题,单一继承并且没有virtual function
class X
{
int x;
};
class Y :public X
{
int y;
};
得到的结果是
1> class X size(4):
1> +---
1> 0 | x
1> +---
1>
1> class Y size(8):
1> +---
1> 0 | +--- (base class X)
1> 0 | | x
1> | +---
1> 4 | y
1> +---
不能看出子类中继承了父类的成员,现在我们在此基础上增加一个虚函数看看
class X
{
int x;
public:
virtual void funX() {}
};
class Y :public X
{
int y;
public:
virtual void funY(){}
};
结果为
1> class X size(8):
1> +---
1> 0 | {vfptr}
1> 4 | x
1> +---
1>
1> X::$vftable@:
1> | &X_meta
1> | 0
1> 0 | &X::funX
1>
1> X::funX this adjustor: 0
1>
1> class Y size(12):
1> +---
1> 0 | +--- (base class X)
1> 0 | | {vfptr}
1> 4 | | x
1> | +---
1> 8 | y
1> +---
1>
1> Y::$vftable@:
1> | &Y_meta
1> | 0
1> 0 | &X::funX
1> 1 | &Y::funY
1>
1> Y::funY this adjustor: 0
注意这个结果里面多出来了一个东西vftable很明显这个根据字面意思可以知道,这是虚函数指针对应的虚函数表。这里还有另一个东西adjustor,这是什么?
adjustor表示虚函数机制执行时,this指针的调整量,假如fun被多态调用的话,那么它的形式如下:
*(this+0)[0]()
总结虚函数调用形式,应该是:
*(this指针+调整量)[虚函数在vftable内的偏移]()
还有最后这个结果中要说的最后一点是,关于vfptr的位置问题,如你所见,在vs2015中它是被编译器放在了类的最前面,但是不是所有的编译器都是这样的。ok,开始下一个话题。
多重继承问题
依然从最简单的开始,不考虑virtual function
class X
{
int x;
};
class Y :public X
{
int y;
};
class Z :public X
{
int z;
};
class A :public Y, public Z
{
int a;
};
结果是
1> class X size(4):
1> +---
1> 0 | x
1> +---
1>
1> class Y size(8):
1> +---
1> 0 | +--- (base class X)
1> 0 | | x
1> | +---
1> 4 | y
1> +---
1>
1> class Z size(8):
1> +---
1> 0 | +--- (base class X)
1> 0 | | x
1> | +---
1> 4 | z
1> +---
1>
1> class A size(20):
1> +---
1> 0 | +--- (base class Y)
1> 0 | | +--- (base class X)
1> 0 | | | x
1> | | +---
1> 4 | | y
1> | +---
1> 8 | +--- (base class Z)
1> 8 | | +--- (base class X)
1> 8 | | | x
1> | | +---
1> 12 | | z
1> | +---
1> 16 | a
1> +---
我们这里注意到A的大小是20,结合结构图不难理解,好,现在增加virtual function
class X
{
int x;
public:
virtual void funX() {}
};
class Y :public X
{
int y;
public:
virtual void funY(){}
};
class Z :public X
{
int z;
public:
virtual void funZ() {}
};
class A :public Y, public Z
{
int a;
public:
virtual void funA() {}
virtual void funY() {}
virtual void funZ() {}
};
结果是
1> class X size(8):
1> +---
1> 0 | {vfptr}
1> 4 | x
1> +---
1>
1> X::$vftable@:
1> | &X_meta
1> | 0
1> 0 | &X::funX
1>
1> X::funX this adjustor: 0
1>
1> class Y size(12):
1> +---
1> 0 | +--- (base class X)
1> 0 | | {vfptr}
1> 4 | | x
1> | +---
1> 8 | y
1> +---
1>
1> Y::$vftable@:
1> | &Y_meta
1> | 0
1> 0 | &X::funX
1> 1 | &Y::funY
1>
1> Y::funY this adjustor: 0
1>
1> class Z size(12):
1> +---
1> 0 | +--- (base class X)
1> 0 | | {vfptr}
1> 4 | | x
1> | +---
1> 8 | z
1> +---
1>
1> Z::$vftable@:
1> | &Z_meta
1> | 0
1> 0 | &X::funX
1> 1 | &Z::funZ
1>
1> Z::funZ this adjustor: 0
1>
1> class A size(28):
1> +---
1> 0 | +--- (base class Y)
1> 0 | | +--- (base class X)
1> 0 | | | {vfptr}
1> 4 | | | x
1> | | +---
1> 8 | | y
1> | +---
1> 12 | +--- (base class Z)
1> 12 | | +--- (base class X)
1> 12 | | | {vfptr}
1> 16 | | | x
1> | | +---
1> 20 | | z
1> | +---
1> 24 | a
1> +---
1>
1> A::$vftable@Y@:
1> | &A_meta
1> | 0
1> 0 | &X::funX
1> 1 | &A::funY
1> 2 | &A::funA
1>
1> A::$vftable@Z@:
1> | -12
1> 0 | &X::funX
1> 1 | &A::funZ
1>
1> A::funA this adjustor: 0
1> A::funY this adjustor: 0
1> A::funZ this adjustor: 12
这里出现一个有意思的问题,在多重继承下,子类有两个虚函数表,分别来自两个父类,其中一个虚函数表中包含了子类的虚函数,而另一个没有。如果子类重写了任意父类的虚函数,都会覆盖对应的函数地址记录。
在这里我们发现A::funZ的函数对应的adjustor值是12,按照我们前边的规则,可以发现该函数的多态调用形式为:
*(this+12)[1]()
此处的调整量12正好是Z的vfptr在A对象内的偏移量。
这里我们注意到在子类A中有两个X,分别是Y中的X和Z中的X,这种做法我们认为是对内存的一种浪费,怎么解决这个问题呢?就引出了虚拟继承。
虚拟继承
class X
{
int x;
public:
virtual void funX() {}
};
class Y :virtual public X
{
int y;
public:
virtual void funY(){}
};
class Z :virtual public X
{
int z;
public:
virtual void funZ() {}
};
class A :public Y, public Z
{
int a;
public:
virtual void funA() {}
virtual void funY() {}
virtual void funZ() {}
};
结果是
1> class X size(8):
1> +---
1> 0 | {vfptr}
1> 4 | x
1> +---
1>
1> X::$vftable@:
1> | &X_meta
1> | 0
1> 0 | &X::funX
1>
1> X::funX this adjustor: 0
1>
1> class Y size(20):
1> +---
1> 0 | {vfptr}
1> 4 | {vbptr}
1> 8 | y
1> +---
1> +--- (virtual base X)
1> 12 | {vfptr}
1> 16 | x
1> +---
1>
1> Y::$vftable@Y@:
1> | &Y_meta
1> | 0
1> 0 | &Y::funY
1>
1> Y::$vbtable@:
1> 0 | -4
1> 1 | 8 (Yd(Y+4)X)
1>
1> Y::$vftable@X@:
1> | -12
1> 0 | &X::funX
1>
1> Y::funY this adjustor: 0
1> vbi: class offset o.vbptr o.vbte fVtorDisp
1> X 12 4 4 0
1>
1> class Z size(20):
1> +---
1> 0 | {vfptr}
1> 4 | {vbptr}
1> 8 | z
1> +---
1> +--- (virtual base X)
1> 12 | {vfptr}
1> 16 | x
1> +---
1>
1> Z::$vftable@Z@:
1> | &Z_meta
1> | 0
1> 0 | &Z::funZ
1>
1> Z::$vbtable@:
1> 0 | -4
1> 1 | 8 (Zd(Z+4)X)
1>
1> Z::$vftable@X@:
1> | -12
1> 0 | &X::funX
1>
1> Z::funZ this adjustor: 0
1> vbi: class offset o.vbptr o.vbte fVtorDisp
1> X 12 4 4 0
1>
1> class A size(36):
1> +---
1> 0 | +--- (base class Y)
1> 0 | | {vfptr}
1> 4 | | {vbptr}
1> 8 | | y
1> | +---
1> 12 | +--- (base class Z)
1> 12 | | {vfptr}
1> 16 | | {vbptr}
1> 20 | | z
1> | +---
1> 24 | a
1> +---
1> +--- (virtual base X)
1> 28 | {vfptr}
1> 32 | x
1> +---
1>
1> A::$vftable@Y@:
1> | &A_meta
1> | 0
1> 0 | &A::funY
1> 1 | &A::funA
1>
1> A::$vftable@Z@:
1> | -12
1> 0 | &A::funZ
1>
1> A::$vbtable@Y@:
1> 0 | -4
1> 1 | 24 (Ad(Y+4)X)
1>
1> A::$vbtable@Z@:
1> 0 | -4
1> 1 | 12 (Ad(Z+4)X)
1>
1> A::$vftable@X@:
1> | -28
1> 0 | &X::funX
1>
1> A::funA this adjustor: 0
1> A::funY this adjustor: 0
1> A::funZ this adjustor: 12
1> vbi: class offset o.vbptr o.vbte fVtorDisp
1> X 28 4 4 0
结果比较复杂,用一张图概括就是
我们可以看到。这里的class A内部只有一个class X了,这张图有个小的错误,就是y和z的vbtable
后面少画了一个0。这个0是作为一种标记,标记这个表的结束位置。vbtable
表中的-4指的是y和z的vbtable
相对于自身的偏移。24表示的是y的vbtable
距离x的vfptr
的偏移,同样的12表示的是z的vbtable
距离x的vfptr
的偏移。至此所有问题全部解决了?
等一下,还有一个问题呢!!!编译器生成的表中有的虚函数表的前面都会有这样 _meta
结尾,这个是什么呢?深度探索c++对象模型(7)