菱形继承和菱形虚继承

继承是C++的一大特点,我们通过菱形继承和菱形虚继承对继承进行进一步的分析。
【菱形继承】
创建一个基类A让B1和B2公有继承于它,让C公有继承B1和B2。
class A
{
public :
    A()
        :a(1)
    {
        cout << "A()" << endl;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
    int a;
};

class B1 : public A
{
public :
    B1()
        :b1(2)
    {
        cout << "B1()" << endl;
    }
    ~B1()
    {
        cout << "~B1()" << endl;
    }
    int b1;
};

class B2 : public A
{
public:
    B2()
        :b2(3)
    {
        cout << "B2()" << endl;
    }
    ~B2()
    {
        cout << "~B2()" << endl;
    }
    int b2;
};

class C : public B1,public B2
{
public:
    C()
        :c(4)
    {
        cout << "C()" << endl;
    }
    ~C()
    {
        cout << "~C()" << endl;
    }
    int c;
};
我们可以通过内存观察C类对象的内存结构,如下图:

我们可以观察到在C类对象创建成功后,C类对象包含了B1和B2类成员,B1和B2的成员中又包含了A类的成员,那么在对象的构造时具体是怎样的过程呢?
利用反汇编查看汇编代码进行分析,如下图:

去掉了一些其他信息,发现在创建C类对象时,先去调用了B1类的构造函数,在B1的构造函数中又去调用了A类的构造函数,返回C类的构造函数中后接着调用B2的构造函数,在B2的构造函数中又去调用A的构造函数。
析构函数的调用与构造函数的调用顺序相反(先创建后销毁)
我们可以通过打印信息验证进一步验证:

注意:可以通过汇编代码看到,在执行派生类构造函数的初始化列表之前就去调用了基类的构造函数。
问题:
  • 当要通过C类对象去访问A类的成员时会出现二义性问题,因为编译器不知道你要访问的是B1类中所包含的A类成员还是B2类中所包含的A类成员。
  • 在C类对象构造的构成中,A类的构造函数被执行了两次(B1和B2分别执行一次),会创建出两个完全一样的问题,这就造成了数据冗余问题。

【菱形虚拟继承】
c++中引入了 虚继承来解决上面的二义性问题和数据冗余的问题。虚拟继承的目的是让某个类作出声明,承诺愿意共享它的基类。

创建一个基类A让B1和B2虚继承于它,让C公有继承B1和B2,这时B1和B2就共享了基类A。

class A
{
public :
    A()
        :a(1)
    {
        cout << "A()" << endl;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
    int a;
};

class B1 :virtual public A
{
public :
    B1()
        :b1(2)
    {
        cout << "B1()" << endl;
    }
    ~B1()
    {
        cout << "~B1()" << endl;
    }
    int b1;
};

class B2 : virtual public A
{
public:
    B2()
        :b2(3)
    {
        cout << "B2()" << endl;
    }
    ~B2()
    {
        cout << "~B2()" << endl;
    }
    int b2;
};

class C : public B1,public B2
{
public:
    C()
        :c(4)
    {
        cout << "C()" << endl;
    }
    ~C()
    {
        cout << "~C()" << endl;
    }
    int c;
};

我们可以观察C类对象在内存中的存储结构:


B1和B2都被实现为对A类的虚继承,说明B1和B2共享了基类的成员,但是我们可以通过上图观察到A类成员既没有和B1类连续也没有和B2类连续,那么怎样访问B1和B2中继承于A类的成员呢?这就用到了B1和B2成员中多出来的那个地址,我们打开内存窗口进行观察:

注意:如果一个派生类虚继承于一个基类那么在创建派生类对象时,会在对象前四个字节中加入一个地址,指向一个表,这个表中存放了两个偏移量,相对于自己的偏移量和相对于基类的偏移量,可以通过偏移量来找到共享的基类成员。
进一步对创建C类对象的创建过程进行分析:
我们通过查看汇编代码进行分析

可以通过汇编代码看到,在创建C类对象时,在执行C类构造函数的初始化列表之前分别调用了A类、B1类和B2类的构造函数,并没有在B1和B2的构造函数中调用A类的构造函数,也就保证了只创建出了一份A类成员。

我们可以通过打印信息进一步验证:


  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++的菱形继承是指一个派生类同时继承了两个直接或间接基类,而这两个基类又间接或直接继承自同一个基类,从而形成了一个菱形继承关系。 例如下面的代码: ``` class A { public: int a; }; class B : public A { public: int b; }; class C : public A { public: int c; }; class D : public B, public C { public: int d; }; ``` 在这个例子中,类 `D` 继承了类 `B` 和类 `C`,而类 `B` 和类 `C` 都继承了类 `A`,因此形成了一个菱形继承关系。 菱形继承会引起一些问题,例如: 1. 内存浪费:由于类 `A` 被重复继承,导致在内存中存在两份相同的 `A` 对象,造成内存浪费。 2. 访问冲突:由于类 `D` 继承了类 `B` 和类 `C`,而这两个类都继承了类 `A`,因此在类 `D` 中访问 `A` 中的成员时会出现访问冲突的问题。 为了解决菱形继承带来的问题,可以使用虚继承虚继承可以解决内存浪费和访问冲突的问题,它的原理是在派生类中只保留一个虚基类的实例,由所有的派生类共享使用。 修改上面的例子,使用虚继承: ``` class A { public: int a; }; class B : virtual public A { public: int b; }; class C : virtual public A { public: int c; }; class D : public B, public C { public: int d; }; ``` 在这个例子中,类 `B` 和类 `C` 继承类 `A` 时使用了 `virtual` 关键字,表示使用虚继承。这样,类 `D` 中就只有一个 `A` 对象的实例,而且访问 `A` 中的成员也不会出现访问冲突的问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值