0053 关于虚继承与虚函数占用字节的探索

1、为什么要引入虚拟继承

虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。实现的代码如下:

class A

class B1:public virtual A;

class B2:public virtual A;

class D:public B1,public B2;

虚拟继承在一般的应用中很少用到,所以也往往被忽视,这也主要是因为在C++中,多重继承是不推荐的,也并不常用,而一旦离开了多重继承,虚拟继承就完全失去了存在的必要因为这样只会降低效率和占用更多的空间。

2.引入虚继承和直接继承会有什么区别呢

由于有了间接性和共享性两个特征,所以决定了虚继承体系下的对象在访问时必然会在时间和空间上与一般情况有较大不同。

2.1时间:在通过继承类对象访问虚基类对象中的成员(包括数据成员和函数成员)时,都必须通过某种间接引用来完成,这样会增加引用寻址时间(就和虚函数一样),其实就是调整this指针以指向虚基类对象,只不过这个调整是运行时间接完成的。

2.2空间:由于共享所以不必要在对象内存中保存多份虚基类子对象的拷贝,这样较之多继承节省空间。虚拟继承与普通继承不同的是,虚拟继承可以防止出现diamond继承时,一个派生类中同时出现了两个基类的子对象。也就是说,为了保证这一点,在虚拟继承情况下,基类子对象的布局是不同于普通继承的。因此,它需要多出一个指向基类子对象的指针。

二、虚继承与虚函数占用字节空间的计算

在VS 2008中写了以下函数(编译器中一般以4的倍数为对齐单位):

class A
    {
    public:
                virtual void  f()
                    {
                        cout<<"A的函数"<<endl;
                    }
    };

    class B:public  A
    {
    public:
                virtual void  f()
                    {
                        cout<<"B的函数"<<endl;
                    }
    };

            cout<<sizeof A<<endl;
            cout<<sizeof B<<endl;

大小:

  • A 4
  • B 4
    分析:由于A基类有一个虚函数(其实不管有多少个都按一个占用计算),所以需要一个虚函数指针,指向一个虚函数表,表里顺序存放着对应虚函数的地址。因此A的占用字节为一个虚函数指针大小4.
    继承类B,其也是有一个虚函数,大小同上,但是由于不是虚继承,所以大小只是其本身4.

    现在将B改成A的虚继承

class A
    {
    public:
                virtual void  f()
                    {
                        cout<<"A的函数"<<endl;
                    }
    };

    class B:public virtual A
    {
    public:
                virtual void  f()
                    {
                        cout<<"B的函数"<<endl;
                    }
    };

            cout<<sizeof A<endl;
            cout<<sizeof B<<endl;

A 4
B 8
分析:sizeof A =4,没问题。但由于B虚继承了A,因此B类需要多加一个虚类指针,指向A。因此sizeof B = 8(两个指针)。
为什么上述中继承类B都没有加上父类A的大小呢?为此我们进行了以下实验:
其他代码不变,将A修改:

class A
    {
    public:
                 void  f()
                    {
                        cout<<"A的函数"<<endl;
                    }
    };

现在 sizeof A = 1 (字节)。可见A是一个空类,因为空类的大小为1。所以得到一个结论,类中的非虚函数不占类的空间。此时输出的结果:
A 1
B 8
而B虚继承了A,sizeof B = sizeof(vptr_B)+sizeof(vptr_B_A)=4+4=8。
可见B虚继承A,但不加上A的大小。

那么何时继承类才会加上基类的大小呢?

在基类A中添加一个char a[3]数组类型,对齐后占用字节4,再加上虚函数指针,所以sizeof A=8.再看看虚继承的B:

class A
    {
    public:
                 char a[3];
                 virtual void  f()
                    {
                        cout<<"A的函数"<<endl;
                    }
    };

    class B:public virtual A
    {
    public:
                virtual void  f()
                    {
                        cout<<"B的函数"<<endl;
                    }
    };

            cout<<sizeof A<<endl;
            cout<<sizeof B<<endl;

结果:
A 8
B 12

已知A占8字节,那么继承类B占用怎么计算的?首先B有虚表指针和虚类指针,占8字节,那么另外的4字节哪里来?肯定是加上基类中的基本数据成员的所占的大小,而不是A中虚表指针的大小。为什么?且看:
我们将B改为普通继承:

class B:public   A
    {
    public:
                virtual void  f()
                    {
                        cout<<"B的函数"<<endl;
                    }
    };

最后结果:
A 8
B 8
sizeof B = sizeof(vptr_B) + A中的数据成员占用的字节大小
可见B中并不包含A的虚表指针的大小!
最后引用一张图作为结论:
这里写图片描述
因此《程序员面试宝典.第四版》中141-142页中的例子(2)问题解释有误,他认为虚继承类B 的大小 等于 B的数据成员对齐大小加上B虚表指针大小,再加上整个sizeof A,不包含虚指针。按以上的实验结论的话,这是不对的。基类中虚表指针并不包含在继承类中。
所以回答标题的提问,继承类只包含基类的数据成员大小(已对齐的),不包含基类虚表指针大小。


本文参考:
http://www.cnblogs.com/BeyondAnyTime/archive/2012/06/05/2537451.html
《程序员面试宝典.第四版》

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值