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日。
热门推荐
海南文昌修缮文南老街,南洋骑楼焕发新生机
扬美古镇:南宁周边的历史文化宝藏
术前检查艾滋病,痔疮手术的安全保障
双色球头奖井喷16注:粤川各中5注揽906万
白洋淀秋景打卡,保定市不容错过的周末游
长白山的神秘传说,你知道几个?
湿狗粮or狗奶?兽医推荐的最佳幼犬饮食
虹螺岘干豆腐:三百年的历史传承与创新
阿咖酚胶囊:成人24小时最多4次,服药期间禁酒
甘肃水务集团创新构建安全生产“六项机制”,打造系统安全管理新模式
杭州一日游打卡七大宝藏景点
《幸福三重奏》:陈建斌的“老小孩”与蒋勤勤的包容之道
双子座:风象星座的二元性与十二种上升表现
幼犬专用狗粮,科学喂养小奶狗的秘密武器
双十一细节表白,让TA感动爆棚
信用评分卡:金融风险管理的利器
训练小奶狗听从基本命令的秘诀
家庭自制水果捞:健康美味的秘密武器
智能语音技术:从精准识别到深度理解,开启人机交互新纪元
许新超教你如何引导孩子情绪
家庭版醋溜白菜,小白也能秒变大厨!
从《丑女无敌》到实力派:刘晓晔的演艺之路
长白山自然保护管理中心:守护绿色奇迹30年
秋葵:便秘救星?科学依据与实用方案
耐碱纳滤膜技术用于碱液中金属的回收优势
搞笑视频教你秒变网易云音乐达人
探访临安茶园,揭秘天目青顶的神秘魅力
O7合金工具钢应用领域详解
看奥运学乒乓,新手速成秘籍
CATTI二级口译考前必看:从听力到笔记的全方位冲刺指南