在写这一主题的文章之前,在网上找到一篇很非常好的文章C++之继承与多态。就没有必要做重复造轮子的事件了,那就从这篇文章开始吧!
在c++中一个类可以从多个基类中派生(即可以有多个父类),这就是多继承。多继承的方式主要有两种:
1. 简单版本
类C会同时拥有类A和类B的特性(属性和方法,也就是两个类的所有成员)。这种方式很简单这里就不多说,主要讲下面这种方式。
2. 复杂版本
2.1. 菱形继承的模型
同样的,这个结构中类C也会同时拥有类B1和类B2的特性,但这就会有一个问题,类B1和B2都继承自A,那么类C的对象会同时包含两个A的对象,这样就会带来很多歧义性。
这个就是典型的菱形继承问题,不少C++的面试官喜欢考这一类问题,希望通过这个问题,考察你对C++面向对象的理解。
我们看一个典型的例子“沙发-床”:
因为沙发
和床
都是家具,所以定义了通用的抽象父类家具
(Furniture),沙发
(Sofa)和床
(Bed)继承这个父类。然而沙发床
,既可以当沙发用也可以当床用,所以沙发床
(SleepSofa)继承了床
(Bed)和沙发
(Sofa)两个类。
代码实现如下:
1 |
|
SleepSofa类继承自Bed和Sofa两个类,因此,SleepSofa类拥有这两个类的特性,但在实际编码中会存在如下几个问题。
2.2. 多继承的类如何定义?
以 SleepSofa 为例,多重继承的类的定义结构如下:
1 | class SleepSofa |
构造顺序为:Bed sofa sleepsofa (也就是书写的顺序)
2.3. 如何解决菱形继承的重复对象问题?
Bed和Sofa类都继承自Furniture,都有Weight属性也都有GetWeight和SetWeight方法,在SleepSofa类中使用这些属性和方法时,如何确定调用的是哪个类的成员?
我们看一下测试样例:
1 | void Test() |
这时会有以下错误:
1 | .cpp(76): error C3861: 'SetWeight': identifier not found |
就是说SetWeight和GetWeight是有歧义的。
2.3.1. 方法一:使用完全限定名
调用是,可以使用完全限定名(即加上类的作用域)的方式,比如:
1 | SleepSofa sleepSofa; |
这时可以看到sleepSofa对象有两个Furniture对象。如下:
2.3.2. 方法二: 虚继承
倘若,我们定义一个SleepSofa对象,让我们分析一下它的构造过程:它会构造Bed类和Sofa类,但Bed类和Sofa类都有一个父类,因此Furniture类被构造了两次,这是不合理的,因此,我们引入了虚继承的概念。
1 | class Furniture{……}; |
这样,Furniture
类就只会构造一次,SleepSofa
的对象只会包含一个Furniture
对象。
我们看一下测试样例:
1 | SleepSofa sleepSofa; |
这时我们Debug模式可以看到SleepSofa
的m_weight
值都是80。
这里虽然显示着两个Furniture
对象,但其实指向的是同一个对象。我们可以看看它们的地址都是一样的。
3. 总结
1. 在程序设计中最好不要出现多继承。
要有也是继承多个作为接口使用抽象类(只有纯虚函数的抽象类),只声明需要的功能,没有具体的实现。
多继承是C++这门语言特有的东西,个人认为这是典型的过渡设计。出现多继承的时候本身就是一种不好的面向对象程序设计。新型的面向对象语言(诞生于C++之后的语言)都没有多继承的概念,Java语言是通过“单继承+接口”的方式来替代多继承的。
2. 在出现版本2的多继承时使用虚继承的方式。
没有其他更好的办法一定要使用多继承的时候,如果出现了菱形继承
,那么就使用虚继承的方式,这样可以避免重复构造。
历史文章推荐:
一文搞懂C/C++常用编译器
C++类的三种访问权限与三种继承方式
C++之迭代器
C/C++如何在main函数开始之前(或结束之后)执行一段逻辑?