C++菱形继承问题

多重继承:一个派生类继承了两个或两个以上的基类。如图

                        
如果在多重继承中Class A 和Class B存在同名数据成员,则对Class C而言这个同名的数据成员容易产生二义性问题。这里的二义性是指无法直接通过变量名进行读取,需要通过域(::)成员运算符进行区分。


菱形继承:属于多重继承的一个特例。如图

        ​​​​​​​        ​​​​​​​        在这里插入图片描述

这种继承方式也存在数据的二义性,这里的二义性是由于他们间接都有相同的基类导致的。 这种菱形继承除了带来二义性之外,还会浪费内存空间

代码如下:

//公共基类
class N
{
public:
    N(int data1, int data2, int data3) : 
        m_data1(data1), 
        m_data2(data2), 
        m_data3(data3)
    {
        std::cout << "call common constructor" << std::endl;
    }
    virtual ~N(){}

    void    display()
    {
        std::cout << m_data1 << std::endl;
    }

public :
    int     m_data1;
    int     m_data2;
    int     m_data3;
};


class A : /*virtual*/ public N
{
public:
    A() :N(11, 12, 13), m_a(1)
    {
        std::cout << "call class A constructor" << std::endl;
    }
    ~A(){}

public :
    int m_a;
};

class B :  /*virtual*/ public N
{
public:
    B() :N(21, 22, 23),m_b(2)
    {
        std::cout << "call class B constructor" << std::endl;
    }
    ~B(){}

public :
    int m_b;
};


class C : public A ,  public B
{
public:
    //负责对基类的初始化
    C() : A(), B(),
        m_c(3)
    {
        std::cout << "call class C constructor" << std::endl;
    }
    void show()
    {
        std::cout << "m_c=" << m_c << std::endl;
    }

 public :
    int m_c;
};

 我们通过vs自带的内存分析模型工具,得到如下的内存分布模型:

 我们发现在类C中存在 两份的基类N,分别存在类A和类B中,如果数据多则严重浪费空间,也不利于维护, 我们引用基类N中的数据还需要通过域运算符进行区分。

虚基类 & 虚基类

为了解决上述菱形继承带来的问题,C++中引入了虚基类,其作用是 在间接继承共同基类时只保留一份基类成员,虚基类的声明如下:

class A//A 基类
{ ... };

//类B是类A的公用派生类, 类A是类B的虚基类
class B : virtual public A
{  ... };

//类C是类A的公用派生类, 类A是类C的虚基类
class C : virtual public A
{  ... };

虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式声明的。

虚继承是声明类时的一种继承方式,在继承属性前面添加virtual关键字。

虚基类的初始化

这里直接说明结论,对于虚基类的初始化是由最后的派生类中负责初始化。

在最后的派生类中不仅要对直接基类进行初始化,还要负责对虚基类初始化。

例如:

 // 类A和类B是虚继承方式
class C : public A, public B
{
public:
    //负责对直接基类的初始化 以及虚基类的初始化
    C() : A(), B(), N(31,32,33) ,
        m_c(3)
    {
        std::cout << "call class C constructor" << std::endl;
    }
    void show()
    {
        std::cout << "m_c=" << m_c << std::endl;
    }

public:
    int m_c;
};

虚基类构造次数

C++编译系统只执行最后的派生类对基类的构造函数调用,而忽略其他派生类对虚基类的构造函数调用。从而避免对基类数据成员重复初始化。因此,虚基类只会构造一次。

总结如下:

 

虚基类的完整分析代码如下:

//公共基类
class N
{
public:
    N(int data1, int data2, int data3) :
        m_data1(data1),
        m_data2(data2),
        m_data3(data3)
    {
        std::cout << "call common constructor" << std::endl;
    }
    virtual ~N(){}

    void    display()
    {
        std::cout << m_data1 << std::endl;
    }

public:
    int     m_data1;
    int     m_data2;
    int     m_data3;
};

//虚继承方式
class A : virtual public N
{
public:
    A() :N(11, 12, 13), m_a(1)
    {
        std::cout << "call class A constructor" << std::endl;
    }
    ~A(){}

public:
    int m_a;
};

//虚继承方式
class B :  virtual public N
{
public:
    B() :N(21, 22, 23), m_b(2)
    {
        std::cout << "call class B constructor" << std::endl;
    }
    ~B(){}

public:
    int m_b;
};

  // 类A和类B是虚继承方式
class C : public A, public B
{
public:
    //负责对直接基类的初始化 以及虚基类的初始化
    C() : A(), B(), N(31,32,33), 
        m_c(3)
    {
        std::cout << "call class C constructor" << std::endl;
    }
    void show()
    {
        std::cout << "m_c=" << m_c << std::endl;
    }

public:
    int m_c;
};


//此时基类N不是虚基类
class D : public N
{
public:
    //负责对基类的初始化
    D() :N(41, 42, 43),
        m_d(4)
    {
        std::cout << "call class D constructor" << std::endl;
    }
    void show()
    {
        std::cout << "m_d=" << m_d << std::endl;
    }

public:
    int m_d;
};


int _tmain(int argc,  _TCHAR* argv[])
{
    C data;   
    //直接使用基类数据
    data.m_data1 = 10;
    
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值