C++深度解析:菱形继承问题与解决方案
创作时间:
作者:
@小白创作中心
C++深度解析:菱形继承问题与解决方案
引用
1
来源
1.
https://cloud.tencent.com/developer/article/1688467
在C++编程中,继承是一个核心概念,但同时也带来了一些复杂的问题。本文将深入探讨菱形继承的定义、问题及其解决方案,以及继承和组合的区别,帮助开发者更好地理解和应用这些概念。
1. 什么是菱形继承?菱形继承的问题是什么?
菱形继承是多继承的一种特殊形式。如下图所示为一个典型的菱形继承结构:
即两个派生类继承同一个基类,同时这两个派生类又作为基类继承给同一个派生类。这种继承形如菱形,故又称为菱形继承。
菱形继承的主要问题是数据冗余和二义性。由于最底层的派生类继承了两个基类,同时这两个基类又继承自同一个基类,这会导致最顶层基类的成员被重复继承,造成数据冗余和访问二义性。例如,在Assistant对象中,Person成员会有两份:
class Person {
public:
string _name; // 姓名
};
class Student : public Person {
protected:
int _num; // 学号
};
class Teacher : public Person {
protected:
int _id; // 职工编号
};
class Assistant : public Student, public Teacher {
protected:
string _majorCourse; // 主修课程
};
void Test() {
// 这样会有二义性无法明确知道访问的是哪一个
Assistant a;
a._name = "peter";
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
}
2. 什么是菱形虚拟继承?如何解决数据冗余和二义性的?
虚拟继承可以有效解决菱形继承的二义性和数据冗余问题。在Student和Teacher继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方使用。
class Person {
public:
string _name; // 姓名
};
class Student : virtual public Person {
protected:
int _num; // 学号
};
class Teacher : virtual public Person {
protected:
int _id; // 职工编号
};
class Assistant : public Student, public Teacher {
protected:
string _majorCourse; // 主修课程
};
void Test() {
Assistant a;
a._name = "peter";
}
虚拟继承解决数据冗余和二义性的原理
通过一个更简单的例子来说明虚拟继承的原理:
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;
};
int main() {
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
下图是菱形虚拟继承的内存对象成员模型:
这里可以分析出D对象中将A放到了对象组成的最下面,这个A同时属于B和C。B和C通过虚基表指针指向虚基表,虚基表中存储了偏移量,通过偏移量可以找到下面的A。
3. 继承和组合的区别?什么时候用继承?什么时候用组合?
- public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
- 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
- 优先使用对象组合,而不是类继承。
- 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见。
- 继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
- 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
- 实际开发中尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。
4. C++中哪些成员函数不能被继承
- 构造函数:在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。如果没有显式的构造函数,编译器会给一个默认的构造函数,并且该默认的构造函数仅仅在没有显式地声明构造函数情况下创建。
- 析构函数:析构函数也不会被子类继承,只是在子类的析构函数中会调用父类的析构函数。
- 运算符重载赋值函数:赋值运算符重载函数也不会被子类继承,只是在子类的赋值运算符重载函数中会调用父类的赋值运算符重载函数。
本文内容来自腾讯云开发者社区,原始发表于2020年7月21日。
热门推荐
【枪械讲堂】承前启后的柯尔特M1872军用转轮手枪
朱崇坤:新《公司法》对股东出资义务的修订要点
新公司法下,“不等比减资”的实操要点及难点破解
带状疱疹手指肿胀怎么消退
拼多多被调包如何维权?法律途径全解析
中医病因病机
《黑神话:悟空》不空和尚打法攻略
电容通交流阻直流原理是什么?电容的功能和作用
雷地豫卦详解:古老卦象中的平衡智慧
林的意思~(林字的含义与象征)
帧率又不对了?详解达芬奇帧率的N个问题
打针后猫咪多久可安全沐浴指南
男性152-188cm标准体重对照表,看你“达标”了吗?
2035广州交通:55地铁,45分钟通勤,“电鸡”有序……
静脉曲张何时进行干预?避免延误的最佳选择
氧化铝的化学式及其特性分析(氧化铝深度研究)
肺栓塞的治疗方法及药物有哪些
肺梗塞的治疗
南沙国际邮轮母港进入常态化运营新阶段,湾区城市联动加强
蒙顶甘露:绿茶中的贵族,凭什么与众不同?
艾滋病终身治疗:揭秘抗逆转录病毒药物的奥秘
关于做好2025年春季新冠病毒感染等重点传染病防治工作的通知
一颗小行星如何终结恐龙时代?揭秘6500万年前的地球浩劫
阿美替尼耐药后怎么治疗
如何评估政策变动对投资的影响
欧奈尔的投资思路拆解!
方大同抗病5年突离世 2星期前刚发布新歌
雅阁混动和凯美瑞混动哪个好
氧化锌再“万能”,有这2种情况时也不能给宝宝用!
深圳医保异地生育报销标准+流程+材料