虚继承详解及其内存分布

什么是虚继承?

根据百度百科:

虚继承 是面向对象编程中的一种技术,是指一个指定的基类,在继承体系结构中,将其成员数据实例共享给也从这个基类型直接或间接派生的其它类。

虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。

如上图:

假设类a是父类,b类和c类都继承了a类,而d类又继承了b和c,那么由于d类进行了两次多重继承a类,就会出现两份相同的a的数据成员或成员函数,就会出现代码冗余。同时,我们在d类的实例化对象中调用从a类继承来的数据成员或者成员函数的时候,无法分清是来自于b类或者是c类,会发生编译错误。这也就是我们常说的二义性。

故为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类,否则仍然会出现对基类的多次继承。那么解决这个问题的关键就是虚继承。

我们直接看代码:

#include <cstring>
#include <iostream>
using namespace std;
class Base_A
{
public:
    Base_A():m_base_a("Base A") {}
    virtual ~Base_A() {}
    //~Base_A() {}
    void printA() { cout << this->m_base_a << endl; }
private:
    string m_base_a;
};

class Derived_B :virtual public Base_A
{
public:
    Derived_B():m_derived_b("Derived B") {}
    virtual ~Derived_B() {}
    void printB() { cout << m_derived_b << endl; }
private:
    string m_derived_b;
};

class Derived_C :virtual public Base_A
{
public:
    Derived_C():m_derived_c("Derived C") {}
    virtual~Derived_C() {}
    void printC() { cout << m_derived_c << endl; }
private:
    string m_derived_c;
};

class Derived_D :public Derived_B, public Derived_C
{
public:
    Derived_D():m_derived_d("Derived D") {}
    virtual ~Derived_D() {}
    void printD() { cout << m_derived_d << endl; }
private:
    string m_derived_d;
};

int main()
{
    Derived_D D;

    cout << sizeof(Base_A) << endl;
    cout << sizeof(Derived_B) << endl;
    cout << sizeof(Derived_C) << endl;
    cout << sizeof(Derived_D) << endl;
    return 0;
}

操作系统为MacOS,IDE为Clion。代码直接输出了4个类的内存大小。

在这我们要注意一下,在我的编程环境下,string类型的大小是24(无论string中是否有内容,内容的多少,均占24个字节)。

我们一步步来分析一下数字是怎么来的。为了方便理解,我们首先看一下没有虚继承的情况。以下简称4个类为ABCD

若没有虚继承:

类A中存在string数据成员,占据24个字节,又因虚析构函数的存在,所以有一个虚函数表指针,占8字节(64bit),24+8=32

类B继承了类A,同时类B也拥有一个自己的string类型数据成员,同时有一个虚函数表指针,共24+24+8=56

类C一样继承了类A,同为56

类D同时继承了类B和类C,即除了自己的string数据成员,还拥有B、C两个类的数据成员,同时由于是多继承,那么类D应该拥有两个虚函数表,即拥有两个虚函数表指针(分别指向B和C的虚函数表)。故24(D类自身的数据成员)+24*4(B和C类的数据成员和两个A类数据成员)+8*2(两个虚函数指针)=136

若有虚继承:

类A不变,还是32

此时类B继承了类A,除了数据成员跟虚函数表指针以外,额外增添了一个指针,该指针指向了从类A中继承过来的数据。也成为数据的共享(虚继承的关键)即56+8=64

类C同理,也为64

此时类D继承C和D,额外再加两个虚函数表指针和一个后来增添的指针(指向共享的数据内容),由于增添的指针指明了共享的数据,故此时类D中只包含一个A中的数据,故大小为24(自身数据成员)+24(A中数据成员)+48(B、C中数据成员)+8*3=120

这也就说明了通过引入虚继承,可以减少内存的占用量。同时,我们在调用基类的成员函数的时候,也不会发生二义性的错误。编译器可以自动通过指针,来找到共享的内容。

但是,很多程序员不提倡在程序中使用多重继承,只有在比较简单和不易出现二义性的情况或实在必要时才使用多重继承,能用单一继承解决的问题就不要使用多重继承。也正由于这个原因,C++之后的很多面向对象的编程语言(如JavaSmalltalkC#PHP等)并不支持多重继承。

  • 0
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值