目录
前文
一,单继承/多継承
二,菱形継承
三,菱形虚拟継承
3.1 虚拟継承的用法
3.2 解决原理
四,継承的总结和反思
总结
前文
书接上文,上篇文章我们讲解了一下継承的基础运用,这节我们讲一下継承中惹人诟病的缺点——菱形継承.
一,单继承/多継承
在将菱形継承之前我们需要先讲解一下单継承和多継承
单継承:一个子类只有一个直接父类时称这个继承关系为单继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承,多继承的出现主要是为了应对生活中的一些场景,如圣女果同时具有水果和蔬菜两种属性,但是可能祖师爷也没有想到这会引出来一个大问题——菱形継承。
二,菱形継承
菱形継承其实就是多继承的一个变种,如下图所示
菱形継承的使用场景也是有的,但是它弊大于利,使用起来问题颇多,主要问题有两个。
1.二义性的问题,如上图,B和C都有A的元素,那么D调用A时,调用的时B中的A元素还是B中的A元素呢。
2.数据冗余的问题,如上图,B和C中都有A,这导致継承BC的D中也有两个A,这就造成了数据冗余。
class A{public:int _a;};class B :public A{protected:int _b;};class C :public A{protected:int _c;};class D :public B, public C{protected:int _d;};int main(){D d;d._a;return 0;}
如上图所示,在访问_a时由于二义性的问题会导致访问不明确,那么我们的祖师爷是怎么解决这个问题呢?
三,菱形虚拟継承
在C++3.0中,C++引入了虚拟継承来解决菱形継承的二义性和数据冗余问题。
3.1 虚拟継承的用法
以上面的例子为例,只需要在B和C継承A时在前面加上关键字virtual即可.
class A{public:int _a;};class B :virtual public A{protected:int _b;};class C :virtual public A{protected:int _c;};class D :public B, public C{protected:int _d;};int main(){D d;d._a;return 0;}
成功运行
3.2 解决原理
那么虚拟継承是如何解决菱形継承的二义性和数据冗余的问题的呢?我们可以借助内存窗口观察对象成员的模型
class A{public:int _a;};class B :public A{public:int _b;};//virtual class C :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;}
此次试验借用上面代码,进行有virtual修饰的継承和无修饰的継承的对象成员内存模型的对比
上图是没有virtual修饰継承的对象成员的内存模型
上图是有virtual修饰的成员变量的内存模型,从上图我们可以得出,D对象将A放到了最下面的空间,这个A同时属于B和C,那么B和C是怎么找到A的呢?这里是通过B和C的两个指针,指向的一张表。这里的指针叫做虚基表指针,这里的表叫做虚基表,虚基表里存着从B或者C到公共区域A的偏移量,然后B和C通过偏移量找到A。
下面是菱形虚拟継承的原理解释
四,継承的总结和反思
1. C++语法复杂,多继承就是一个很好的体现。本来多继承的出现是为了处理生活中的某些情况,但是由于多继承的出现,就存在菱形继承,有了菱形継承就需要有菱形虚拟継承,这样底层实现就相当复杂,所以一般建议多继承可以用,但是一定不用设计出菱形継承,否则后续的问题会很难解决。
2. 多継承可以认为是C++设计的缺陷,因此后来的很多OO语言(面向对象语言)都没有多继承,如java
3. 継承和组合
1. public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
2. 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
3. 优先使用对象组合,而不是类继承 。
4. 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
5. 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
6.实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。
总结
上面就是継承的所有内容,希望铁子们可以有所收货