C++(虚)继承类的内存占用大小

首先,平时所声明的类只是一种类型定义,它本身是没有大小可言的。 因此,如果用sizeof运算符对一个类型名操作,那得到的是具有该类型实体的大小。

计算一个类对象的大小时的规律:

1、空类、单一继承的空类、多重继承的空类所占空间大小为:1(字节,下同);

2、一个类中,虚函数本身、成员函数(包括静态与非静态)和静态数据成员都是不占用类对象的存储空间的;

3、因此一个对象的大小≥所有非静态成员大小的总和;

4、当类中声明了虚函数(不管是1个还是多个),那么在实例化对象时,编译器会自动在对象里安插一个指针vPtr指向虚函数表VTable;

5、虚承继的情况:由于涉及到虚函数表和虚基表,会同时增加一个(多重虚继承下对应多个)vfPtr指针指向虚函数表vfTable和一个vbPtr指针指向虚基表vbTable,这两者所占的空间大小为:8(或8乘以多继承时父类的个数);

6、在考虑以上内容所占空间的大小时,还要注意编译器下的“补齐”padding的影响,即编译器会插入多余的字节补齐;

7、类对象的大小=各非静态数据成员(包括父类的非静态数据成员但都不包括所有的成员函数)的总和+ vfptr指针(多继承下可能不止一个)+vbptr指针(多继承下可能不止一个)+编译器额外增加的字节。

示例一:含有普通继承


  1. class A       
  2. {       
  3. };      
  4.    
  5. class B       
  6. {    
  7.     char ch;       
  8.     virtual void func0()  {  }     
  9. };     
  10.    
  11. class C      
  12. {    
  13.     char ch1;    
  14.     char ch2;    
  15.     virtual void func()  {  }      
  16.     virtual void func1()  {  }     
  17. };    
  18.    
  19. class D: public A, public C    
  20. {       
  21.     int d;       
  22.     virtual void func()  {  }     
  23.     virtual void func1()  {  }    
  24. };       
  25.    
  26. class E: public B, public C    
  27. {       
  28.     int e;       
  29.     virtual void func0()  {  }     
  30.     virtual void func1()  {  }    
  31. };    
  32.    
  33. int main(void)    
  34. {    
  35.     cout<<"A="<<sizeof(A)<<endl;    //result=1    
  36.     cout<<"B="<<sizeof(B)<<endl;    //result=8        
  37.     cout<<"C="<<sizeof(C)<<endl;    //result=8    
  38.     cout<<"D="<<sizeof(D)<<endl;    //result=12    
  39.     cout<<"E="<<sizeof(E)<<endl;    //result=20    
  40.     return 0;    
  41. }  

前面三个A、B、C类的内存占用空间大小就不需要解释了,注意一下内存对齐就可以理解了。

求sizeof(D)的时候,需要明白,首先VPTR指向的虚函数表中保存的是类D中的两个虚函数的地址,然后存放基类C中的两个数据成员ch1、ch2,注意内存对齐,然后存放数据成员d,这样4+4+4=12。

求sizeof(E)的时候,首先是类B的虚函数地址,然后类B中的数据成员,再然后是类C的虚函数地址,然后类C中的数据成员,最后是类E中的数据成员e,同样注意内存对齐,这样4+4+4+4+4=20。

示例二:含有虚继承

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. class CommonBase    
  2. {    
  3.     int co;    
  4. };    
  5.    
  6. class Base1: virtual public CommonBase    
  7. {    
  8. public:    
  9.     virtual void print1() {  }    
  10.     virtual void print2() {  }    
  11. private:    
  12.     int b1;    
  13. };    
  14.    
  15. class Base2: virtual public CommonBase    
  16. {    
  17. public:    
  18.     virtual void dump1() {  }    
  19.     virtual void dump2() {  }    
  20. private:    
  21.     int b2;    
  22. };    
  23.    
  24. class Derived: public Base1, public Base2    
  25. {    
  26. public:    
  27.     void print2() {  }    
  28.     void dump2() {  }    
  29. private:    
  30.     int d;    
  31. };  

sizeof(Derived)=32,其在内存中分布的情况如下:

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. class Derived size(32):    
  2.      +---    
  3.      | +--- (base class Base1)    
  4.  | | {vfptr}    
  5.  | | {vbptr}    
  6.  | | b1    
  7.      | +---    
  8.      | +--- (base class Base2)    
  9.  | | {vfptr}    
  10.  | | {vbptr}    
  11.  | | b2    
  12.     | +---    
  13.  | d    
  14.     +---    
  15.     +--- (virtual base CommonBase)    
  16.  | co    
  17.     +---  

示例3:

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. class A    
  2. {    
  3. public:    
  4.     virtual void aa() {  }    
  5.     virtual void aa2() {  }    
  6. private:    
  7.     char ch[3];    
  8. };    
  9.    
  10. class B: virtual public A    
  11. {    
  12. public:    
  13.     virtual void bb() {  }    
  14.     virtual void bb2() {  }    
  15. };    
  16.    
  17. int main(void)    
  18. {    
  19.     cout<<"A's size is "<<sizeof(A)<<endl;    
  20.     cout<<"B's size is "<<sizeof(B)<<endl;    
  21.     return 0;    
  22. }  

执行结果:A’s size is 8
                        B’s size is 16

说明:对于虚继承,类B因为有自己的虚函数,所以它本身有一个虚指针,指向自己的虚表。另外,类B虚继承类A时,首先要通过加入一个虚指针来指向父类A,然后还要包含父类A的所有内容。因此是4+4+8=16。

两种多态实现机制及其优缺点

除了c++的这种多态的实现机制之外,还有另外一种实现机制,也是查表,不过是按名称查表,是smalltalk等语言的实现机制。这两种方法的优缺点如下:

(1)、按照绝对位置查表,这种方法由于编译阶段已经做好了索引和表项(如上面的call *(pa->vptr[1]) ),所以运行速度比较快;缺点是:当A的virtual成员比较多(比如1000个),而B重写的成员比较少(比如2个),这种时候,B的vtableB的剩下的998个表项都是放A中的virtual成员函数的指针,如果这个派生体系比较大的时候,就浪费了很多的空间。

比如:GUI库,以MFC库为例,MFC有很多类,都是一个继承体系;而且很多时候每个类只是1,2个成员函数需要在派生类重写,如果用C++的虚函数机制,每个类有一个虚表,每个表里面有大量的重复,就会造成空间利用率不高。于是MFC的消息映射机制不用虚函数,而用第二种方法来实现多态,那就是:

(2)、按照函数名称查表,这种方案可以避免如上的问题;但是由于要比较名称,有时候要遍历所有的继承结构,时间效率性能不是很高。

3、总结:

如果继承体系的基类的virtual成员不多,而且在派生类要重写的部分占了其中的大多数时候,用C++的虚函数机制是比较好的;但是如果继承体系的基类的virtual成员很多,或者是继承体系比较庞大的时候,而且派生类中需要重写的部分比较少,那就用名称查找表,这样效率会高一些,很多的GUI库都是这样的,比如MFC,QT。PS:其实,自从计算机出现之后,时间和空间就成了永恒的主题,因为两者在98%的情况下都无法协调,此长彼消;这个就是计算机科学中的根本瓶颈之所在。软件科学和算法的发展,就看能不能突破这对时空权衡了。呵呵。。

何止计算机科学如此,整个宇宙又何尝不是如此呢?最基本的宇宙之谜,还是时间和空间。

C++如何不用虚函数实现多态,可以考虑使用函数指针来实现多态

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #include<iostream>    
  2. using namespace std;    
  3.    
  4. typedef void (*fVoid)();    
  5.    
  6. class A    
  7. {    
  8. public:    
  9.     static void test()    
  10.     {    
  11.         printf("hello A\n");    
  12.     }    
  13.    
  14.     fVoid print;    
  15.    
  16.     A()    
  17.     {    
  18.         print = A::test;    
  19.     }    
  20. };    
  21.    
  22. class B : public A    
  23. {    
  24. public:    
  25.     static void test()    
  26.     {    
  27.         printf("hello B\n");    
  28.     }    
  29.    
  30.     B()    
  31.     {    
  32.         print = B::test;    
  33.     }    
  34. };    
  35.    
  36.    
  37. int main(void)    
  38. {    
  39.     A aa;    
  40.     aa.print();    
  41.    
  42.     B b;    
  43.     A* a = &b;    
  44.     a->print();    
  45.    
  46.     return 0;    
  47. }  

这样做的好处主要是绕过了vtable。我们都知道虚函数表有时候会带来一些性能损失。

转载地址:http://www.chepoo.com/c-virtual-class-mem.html



  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值