深入解析C++多重继承:从构造析构到二义性问题全攻略

目录

多继承

多重继承的派生类对象的构造和析构

多重继承可能引发的问题

成员名访问冲突的二义性

存储二义性的问题(重要)


多继承

C++ 除了支持单继承外,还支持多重继承。那为什么要引入多重继承呢?其实是因为在客观现实世界中,我们经常碰到一个人身兼数职的情况,如在学校里,一个同学可能既是一个班的班长,又是学生中某个部门的部长;在创业公司中,某人既是软件研发部的 CTO ,又是财务部的 CFO ;一个人既是程序员,又是段子手。诸如此类的情况出现时,单一继承解决不了问题,就可以采用多基继承了。

继承关系本质上是一个IS A的关系。

多重继承的派生类对象的构造和析构

多继承的定义方式

class A
{
public:
    A(){ cout << "A()" << endl; }
    ~A(){ cout << "~A()" << endl; }
    void print() const{
        cout << "A::print()" << endl;
    }
};

class B
{
public:
    B(){ cout << "B()" << endl; }
    ~B(){ cout << "~B()" << endl; }
    void show() const{
        cout << "B::show()" << endl;
    }
};

class C
{
public:
    C(){cout << "C()" << endl; }
    ~C(){ cout << "~C()" << endl; }
    void display() const{
        cout << "C::display()" << endl;
    }
};

class D
: public A,B,C
{
public:
    D(){ cout << "D()" << endl; }
    ~D(){ cout << "~D()" << endl; }
    //void print() const{
    //    cout << "D::print()" << endl;
    //}
};

如果这样定义,那么D类公有继承了A类,但是对B/C类采用的默认的继承方式是private

如果想要公有继承A/B/C三个类

class D
: public A
, public B
, public C
{
public:
    D(){ cout << "D()" << endl; }
    ~D(){ cout << "~D()" << endl; }
    //void print() const{
        //cout << "D::print()" << endl;
    //}
};

此结构下创建D类对象时,这四个类的构造函数调用顺序如何?

马上调用D类的构造函数,在此过程中会根据继承的声明顺序,依次调用A/B/C的构造函数,创建出这三个类的基类子对象

D类对象销毁时,这四个类的析构函数调用顺序如何?

马上调用D类的析构函数,析构函数执行完后,按照继承的声明顺序的逆序,依次调用A/B/C的析构函数

多重继承可能引发的问题

成员名访问冲突的二义性

解决成员名访问冲突的方法:加类作用域(不推荐)—— 应该尽量避免。

同时,如果D类中声明了同名的成员,可以对基类的这些成员造成隐藏效果,那么就可以直接通过成员名进行访问。

    D d;
    d.A::print();
    d.B::print();
    d.C::print();
    d.print(); //ok

存储二义性的问题(重要)

菱形继承结构

class A
{
public:
    void print() const{
        cout << "A::print()" << endl;
    }
    double _a;
};

class B
: public A
{
public:
    double _b;
};

class C
: public A
{
public:
    double _c;
};

class D
: public B
, public C
{
public:
    double _d;
};

菱形继承情况下,D类对象的创建会生成一个B类子对象,其中包含一个A类子对象;还会生成一个C类子对象,其中也包含一个A类子对象。所以D类对象的内存布局中有多个A类子对象,访问继承自A的成员时会发生二义性(无论是否涉及A类的数据成员,单纯访问A类的成员函数也会冲突)

因为编译器需要通过基类子对象去调用,但是不知道应该调用哪个基类子对象的成员函数。

当然,D类如果再写一个同名成员函数,会发生隐藏。或者使用作用域限定的方式访问 print() 。

解决存储二义性的方法:中间层的基类采用虚继承顶层基类的方式解决存储二义性

class A
{
public:
    void print() const{
        cout << "A::print()" << endl;
    }
    double _a;
};

class B
: virtual public A
{
public:
    double _b;
};

class C
: virtual public A
{
public:
    double _c;
};

class D
: public B
, public C
{
public:
    double _d;
};

采用虚拟继承的方式处理菱形继承问题,实际上改变了派生类的内存布局。B类和C类对象的内存布局中多出一个虚基类指针,位于所占内存空间的起始位置,同时继承自A类的内容被放在了这片空间的最后位置。D类对象中只会有一份A类的基类子对象。

通过VS可以验证,查看D类的布局:

重新生成解决方案,然后再输出栏 ctrl+f 然后输入要查看的class的结构就可以找到了。

验证得到的结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值