类存在虚继承,虚函数的内存占用问题

最近在准备找工作,复习的过程中,遇到了求解含有虚继承、虚函数的类的内存大小计算问题(也就是sizeof的结果)。在这里,做一些总结以便后来者更易理解。

1、我们知道,一个空类的sizeof值为1.

2、加入一个虚函数后,其sizeof值为4,是因为对于类A,编译器为其建立了一个虚表,而A中保存了一份指向虚表的指针,指针就是一个地址,在32位(x86)下,地址的大小为4个字节,所以sizeof值为4。

3、当类B继承A后,求其sizeof值也为4,是因为类B中保存了类A的一个副本,sizeof(B)就相当于它本身成员的大小再加上sizeof(A),而B中没有成员,故值为4。

4、但如果B中拥有自己的虚函数时,sizeof(B)的值依然为4,是因为编译器并没有为B也生成一个虚表,而是将B的虚函数放在A的虚函数下面,所以B只要包含A的副本也就拥有了指向这个虚表的指针。

5、如果B虚继承了A,这时候sizeof(B)却变成了12,分析类的结构(VS中通过右键.cpp文件-->属性-->左侧的C/C++->命令行,然后在其他选项这里写上/d1 reportAllClassLayout  ,这时候再运行,将输出中的显示输出来源改为“生成”,就可以在输出中看到所有类的结构)。

分析发现,编译器为B生成了属于自己的虚表,赋予了B一个指向虚表的指针(上图第一个 vfptr),然后又赋予B一个指向虚继承表的指针(vbptr),紧接着是父类A的一个副本。也是就sizeof(B) = 一个指向B虚表的指针 + 一个指向虚继承表的指针 + sizeof(A) = 12。当然如果B没有自己的虚函数b(),也就不会有指向自己虚表的指针。

6、如果现在一个新的类C,虚继承类B的话,我们可想而知sizeof(C) = 一个指向C虚表的指针 + 一个指向虚继承表的指针 + sizeof(B) = 20。如果C没有虚继承B,也可以理解为,C得到了B的虚表指针,而且会将自己的虚函数放在B的虚函数后面,所以sizeof(C)  = sizeof(B) 。

7、回到第5条,如果B中没有自己的虚函数,而只是重写了父类的虚函数。我们知道其sizeof(B) 应该为8。这是因为B中只含有一个指向虚继承表的指针和父类A的副本。

如果类A拥有自己重载的构造函数呢?我们知道构造函数、析构函数并不影响类的大小,所以sizeof(A)的值并不会变,sizeof(B)也不会变。但是如果类B拥有了自己重载的构造函数呢?答案并不是你想象中的没有变化,sizeof(A)结果不会变化,这点毫无疑问,但是sizeof(B)却变成了12。

这是为什么?不是说好加入构造函数不影响类的大小么?那么我们分析一下这时候类B的结构。

在类B中,第一个vbptr是由于虚继承,产生的一个指向父类A的指针,然后出现了一个指向拥有重写虚函数的父类A的vtordisp指针,最后才是父类A的副本,也就是vtordisp指针使得sizeof(B)变成了12。百度一下,找到了答案。

如果虚拟继承中派生类重写了基类的虚函数,并且在构造函数或者析构函数中使用指向基类的指针调用了该函数,编译器会为虚基类添加vtordisp域。虽然它并不一定被使用,但是我们必须将它添加以防用户在构造函数或析构函数中调用虚函数。
我们知道,如果没有给类写构造函数,编译器会自动给类一个默认的构造函数,而这个构造函数由于是自动生成的,那也就保证了它绝对不可能自行调用父类的虚函数。但是如果我们重载了构造函数,系统会给类添加上这个vtordisp域以防止我们调用。

这也就解释了为什么sizeof(B)等于12的原因了。如果现在有个类C虚继承了类B,然后重写了从A继承来的虚函数,且拥有了自己的构造函数,那么sizeof(C) =  需要一个指向拥有重写虚函数a()的父类A的vtordisp指针而B中已经含有这个vtordisp指针(0) + 一个指向虚继承表的指针(4) + sizeof(B)  = 0 + 4 + 12 = 16。

那么对于下面这种复杂的菱形继承情况。容易计算,

sizeof(A) = 一个指向虚表的指针 = 4

sizeof(B) = 由于虚继承且有自己的虚函数而产生的一个指向自己虚表的指针 + 由于虚继承而产生的一个指向虚继承表指针 + 由于自己拥有构造函数而产生的一个指向重写虚函数的创造者A的vtordisp指针 + sizeof(A) = 16

sizeof(C) = 由于虚继承而产生的一个指向虚继承表指针 + 由于虚继承且有自己的虚函数而产生的一个指向自己虚表的指针 + sizeof(A) = 12

最后我们分析sizeof(D) :

I.它继承C,所以它的虚函数d()放在了C的虚表后面,从而没有增加大小;

II.它继承C的同时也得到了C的虚继承表,并将自己虚继承B的信息放在C的虚继承表后,从而没有增加大小;

III.它虚继承了B,重写了虚函数b()、a(),且拥有自己的构造函数,所以应该需要一个指向拥有重写虚函数a()的父类A的vtordisp指针和一个指向拥有重写虚函数b()的父类B的vtordisp指针,但由于B中已经有了这个指向拥有重写虚函数a()的父类A的vtordisp指针,从而也没有增加大小,但没有指向拥有重写虚函数b()的父类B的vtordisp指针,故这部分size相当于4。

IV.大家也知道菱形虚继承所解决的问题就是D中含有重复祖父类A的副本,故计算size时需要减去一个sizeof(A)。

综上sizeof(D) = 由于没有虚继承C而没有增加新的虚表指针(0)+ 由于继承C得到了C的虚继承表而没有增加新的虚继承表指针(0)+由于B中含有指向拥有重写虚函数a()的父类A的vtordisp指针所以不需要再添加(0)+ 由于D中含有构造函数且重写了b()而需要指向拥有重写虚函数b()的父类B的vtordisp指针(4)+ sizeof(B) + sizeof(C) - sizeof(A) = 0 + 0 + 0 + 4 + 12 + 16 - 4 = 28。

所以我们如果修改为D也虚继承C的话,sizeof(D) = 由于虚继承B、C且自己拥有虚函数d()而增加的指向自己虚表的指针(4)+  由于虚继承B、C从而需要重新创建自己的虚继承表(4)+  由于B中含有指向拥有重写虚函数a()的父类A的vtordisp指针所以不需要再添加(0)+ 由于C中不含有指向拥有重写虚函数b()的父类B的vtordisp指针所以添加指向拥有重写虚函数b()的父类B的vtordisp指针(4)+ sizeof(B) + sizeof(C) - sizeof(A)  -  由于C中没有指向拥有重写虚函数a()的父类A的vtordisp指针,所以不需要再减去相同的vtordisp指针(0)= 4 + 4 +0 + 4 +12 + 16 - 4  - 0 = 36。

至此,总结如下四条:

A. 虚继承中,派生类需要添加一个指向虚继承表的指针,而将相关继承信息放到该表中。

B. 虚继承中,如果派生类还有属于自己的虚函数,则需要建立自己的虚表,并在类中添加指针指向该虚表。

C. 虚继承中,如果派生类重写了基类的虚函数,并拥有自己重载的构造函数或析构函数,则类会添加 被重写的虚函数的构造类vtordisp域指针。而这种指针会在继续被子类的虚继承中生效,而不需要生成新的 vtordisp域指针

D. 在多继承情况下,非虚继承关系会使派生类获得此父类的虚继承表和虚表,从而将 与其他基类的虚继承信息 和 虚函数 放在这个虚继承表虚表 后,而不需要重新建立新的表,也就不需要添加新的指针。

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值