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日。
热门推荐
从文化到心理:揭秘《超级马里奥兄弟》右向行走的设计之谜
生产力局运用工业4.0技术,助推佛山企业升级智能制造
中医药在皮肤病治疗中的潜在市场机会
咖啡与茶的历史:你需要知道的九件事
蓝玫瑰的花语及适合赠送的人群
落水救人案件:见义勇为与法律责任的法律适用分析
【健康】荷叶羹、酸梅汤、香薷饮……这些经典夏日食疗方你都知道吗?
脑出血,危及生命的严重危险
企业必存!安全台账参考模板
“大莲花”背水一战,国足能否终止连败?|世预赛前瞻
【法官以案说法】 未成年人无证违法驾驶摩托车,“驶”不得……
电影网站是否违法?法律界对非法电影网站的界定与处罚
就业协议书签订注意事项:避免随意签署,保障自身权益
新冠后遗症的临床特征、发病机制和未来方向
革新食管癌治疗:解读权威文献揭示放疗剂量与靶区选择的前沿策略
科目二侧方位停车技巧图解,轻松掌握停车秘籍
控制图的应用与分析技巧,提升质量管理水平
电热水器使用与维护的全面指南
如何正确使用地暖以节省能源?这种使用方法有哪些实用的小技巧?
如何高效使用错题本?这份实用指南请收好
探秘锂离子电池:从原理到结构的全方位解析
杨柳青木版年画:传承与创新中的中国文化遗产
耳机维修指南:两款经典耳机的耳罩更换全攻略
录的视频怎么消除杂音?从工具到方法一文讲清
肠癌不可怕,可怕的是你不知道这三招
专家详解:咳痰的原因、治疗与调理建议
开工装修仪式需要准备哪些东西:实用指南和注意事项
都是五号电池,1.2V和1.5V有何不同
巧夺“鼻”功—筷子技术提出者福洛里希教授内镜手术理念和案例集
中国十二大名山海拔高度盘点,榜首超过三千米,来自西南地区