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日。
热门推荐
轮式遥控无人侦察车技术详解
2岁和12岁儿童鞋码选择指南
三国杀高顺技能解析
信用风险视角解读:AI对英特尔和英伟达的财务影响?
万用表大揭秘:从构造到应用的实用指南
日照站客运设施改造工程有序推进,预计2025年全面完成
全球逾4亿人患糖尿病 营养师推荐6种鱼降血糖 这种更可降胆固醇
掌握节奏 吸入能量 呼出压力
注塑冰箱盖变形的修补方法(快速解决变形冰箱盖问题的实用技巧)
“辅警”和“协警”仅有一字之差,待遇却天差地别,应届生要注意
专科动物医学好就业吗?和本科动物医学的区别在哪?
复方氨酚烷胺片和布洛芬能一起吃吗
美“下一代空中优势”项目遇阻
紫金山登山全攻略:不同入口路线耗时及注意事项
脉率正常范围,掌握心跳速率标准
培养孩子的智力,围棋真的有必要学吗?
高血糖人群饮食指南:十大要点助你稳定血糖
你的口头禅,往往决定了你的命运,并非迷信,别不当一回事
音乐是一个丰富的世界,我们在其中阅历人生
恋爱中的甜蜜互动:探索情侣间的小秘密
中国人民大学2025年高校专项计划招生简章要点汇总
高考录取分数线的波动?主要是受这五个因素的影响
身份证号码的法律意义与社会功能
揭秘:高阳公主为何会爱上玄奘徒弟辩机?
高考鲁迅题型有哪些?
重庆渣滓洞深度游:历史、景点与周边游览全攻略
未来牧业新宠:水培大麦牧草的营养密码与动物福利革命
起发动机空转的原因是什么?怎样解决起发动机空转的问题?
坐骨神经痛症状、成因及4种治疗方法,附舒缓椎间盘突出运动
保障支付安全,微信一定要做的9个设置