Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

在写这一主题的文章之前,在网上找到一篇很非常好的文章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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#pragma once

#include <iostream>
#include <string>

using namespace std;

class Furniture
{
public:
Furniture(void)
: m_weight(0)
{
}
Furniture(double weight)
: m_weight(weight)
{
}
~Furniture(void) {}

double GetWeight() const { return m_weight; }
void SetWeight(double val) { m_weight = val; }

private:
double m_weight;
};

class Bed : public Furniture
{
public:
Bed()
: Furniture()
, m_second(0)
{
}
Bed(double weight, int second)
: Furniture(weight)
, m_second(second)
{
}

void Sleep(int second)
{
m_second = second;
cout << "休息" << m_second << "秒..." << endl;
}

private:
int m_second;
};

class Sofa : public Furniture
{
public:
Sofa()
: Furniture()
{
}
Sofa(double weight)
: Furniture(weight)
{
}

void WatchTV(string programme) { cout << "正在看" << programme << "节目..." << endl; }
};

class SleepSofa
: public Bed
, public Sofa
{
public:
SleepSofa()
: Bed()
, Sofa()
{
}
SleepSofa(double weight, int second)
: Bed(weight, second)
, Sofa(weight)
{
}

void FoldOut()
{
cout << "展开沙发当床用." << endl;
Sleep(360);
}
};

SleepSofa类继承自Bed和Sofa两个类,因此,SleepSofa类拥有这两个类的特性,但在实际编码中会存在如下几个问题。

2.2. 多继承的类如何定义?

以 SleepSofa 为例,多重继承的类的定义结构如下:

1
2
3
4
5
6
class SleepSofa
: public Bed
, public Sofa
{
// …
};

构造顺序为:Bed sofa sleepsofa (也就是书写的顺序)

2.3. 如何解决菱形继承的重复对象问题?

Bed和Sofa类都继承自Furniture,都有Weight属性也都有GetWeight和SetWeight方法,在SleepSofa类中使用这些属性和方法时,如何确定调用的是哪个类的成员?

我们看一下测试样例:

1
2
3
4
5
6
void Test()
{
SleepSofa sleepSofa;
sleepSofa.SetWeight(55);
double weight = sleepSofa.GetWeight();
}

这时会有以下错误:

1
2
.cpp(76): error C3861: 'SetWeight': identifier not found
error C2385: ambiguous access of 'GetWeight'

就是说SetWeight和GetWeight是有歧义的。

2.3.1. 方法一:使用完全限定名

调用是,可以使用完全限定名(即加上类的作用域)的方式,比如:

1
2
3
SleepSofa sleepSofa;
sleepSofa.Bed::SetWeight(55);
sleepSofa.Sofa::SetWeight(80);

这时可以看到sleepSofa对象有两个Furniture对象。如下:

2.3.2. 方法二: 虚继承

倘若,我们定义一个SleepSofa对象,让我们分析一下它的构造过程:它会构造Bed类和Sofa类,但Bed类和Sofa类都有一个父类,因此Furniture类被构造了两次,这是不合理的,因此,我们引入了虚继承的概念。

1
2
3
4
5
6
7
class Furniture{……};

class Bed : virtual public Furniture{……}; // 这里我们使用虚继承

class Sofa : virtual public Furniture{……};// 这里我们使用虚继承

class SleepSofa : public Bed, public Sofa {……};

这样,Furniture类就只会构造一次,SleepSofa的对象只会包含一个Furniture对象。
我们看一下测试样例:

1
2
SleepSofa sleepSofa;
sleepSofa.SetWeight(80);

这时我们Debug模式可以看到SleepSofam_weight值都是80。

这里虽然显示着两个Furniture对象,但其实指向的是同一个对象。我们可以看看它们的地址都是一样的。

3. 总结

1. 在程序设计中最好不要出现多继承。

要有也是继承多个作为接口使用抽象类(只有纯虚函数的抽象类),只声明需要的功能,没有具体的实现。

多继承是C++这门语言特有的东西,个人认为这是典型的过渡设计。出现多继承的时候本身就是一种不好的面向对象程序设计。新型的面向对象语言(诞生于C++之后的语言)都没有多继承的概念,Java语言是通过“单继承+接口”的方式来替代多继承的。

2. 在出现版本2的多继承时使用虚继承的方式。

没有其他更好的办法一定要使用多继承的时候,如果出现了菱形继承,那么就使用虚继承的方式,这样可以避免重复构造。

历史文章推荐:

一文搞懂C/C++常用编译器
C++类的三种访问权限与三种继承方式
C++之迭代器
C/C++如何在main函数开始之前(或结束之后)执行一段逻辑?

推荐阅读
C++类的三种访问权限与三种继承方式 C++类的三种访问权限与三种继承方式 C 和 C++ struct 的区别 C 和 C++ struct 的区别 C++数据格式化1 - uint转换成字符串 & double转换成字符串 C++数据格式化1 - uint转换成字符串 & double转换成字符串

评论