多重继承引起的二义性
多重继承能够很好地描述现实世界中纷繁复杂的情况,实现了代码的复用,提高了编程效率。但多重继承存在二义性问题,需要大家注意并解决。在多重继承中存在两种二义性问题,下面分别进行介绍。
1、调用不同基类中的同名成员时产生二义性
多个基类出现同名成员时,派生类对象访问该成员时会出现二义性。接下来以一个案例说明派生类对象访问基类同名函数产生的二义性问题,如例1所示。
例1
1 #include <iostream>
2 using namespace std;
3
4 class Bird //定义鸟类
5 {
6 public:
7 //定义鸟呼吸的成员函数
8 void breath() { cout << "bird breath!" << endl; }
9 };
10 class Fish //定义鱼类
11 {
12 public:
13 //定义鱼呼吸的成员函数
14 void breath() { cout << "fish breath!" << endl; }
15 };
16 class WaterBird:public Bird, public Fish //定义水鸟类
17 {
18 public:
19 //定义水鸟行为的成员函数
20 void fly_swim() { cout << "waterbird cat fly and swim!" << endl; }
21 };
22
23 int main()
24 {
25 WaterBird waterbird; //定义水鸟对象
26 //错误,由于WaterBird继承自两个基类,两个基类中存在同名函数breath()
27 //水鸟类对象调用breath()函数时产生调用基类同名函数的二义性
28 waterbird.breath();
29 system("pause");
30 return 0;
31 }
程序编译时出错,错误提示如图1所示。
图1 例1编译错误提示
例1中定义了派生类对象waterbird,并通过派生类对象调用breath()函数,产生编译错误,提示对breath()的访问不明确。观察程序发现,基类Bird、Fish中都定义了函数breath(),派生类从两个基类继承了同名函数,产生了多重继承中调用不同基类中同名成员时的二义性问题。此时,派生类与基类的继承关系及其中成员如图2所示。
图2 派生类WaterBird与其基类的继承关系
从图2看出,在派生类中存在两个同名breath()函数,产生了派生类对象调用该函数的二义性,可以通过以下两种方法,消除这种二义性。
(1)使用作用域限定符
在调用breath()函数时指定类名,将例3-13的main()函数改写为如下内容:
int main()
{
WaterBird waterbird; //定义水鸟对象
waterbird.Bird::breath(); //调用类Bird的breath()函数
waterbird.Fish::breath(); //调用类Fish的breath()函数
system("pause");
return 0;
}
代码修改后,程序运行结果如图3所示。
图3 通过作用域运算符调用不同基类的同名函数
上述代码中通过使用作用域运算符明确了被调函数的所属类,消除了二义性,但需要知道类的继承层次信息,增加了程序开发的复杂度。
(2)派生类中定义与基类同名函数,将基类函数隐藏
若派生类中存在与基类同名的成员函数,则会隐藏基类函数,将例3-14中派生类WaterBird定义进行修改,增加breath()函数,代码如下
class WaterBird:public Bird, public Fish //定义水鸟类
{
public:
//定义水鸟行为的成员函数
void fly_swim() { cout << "waterbird cat fly and swim!" << endl; }
//定义水鸟呼吸的成员函数
void breath() { cout << "waterbird breath!" << endl; }
};
WaterBird类中增加breath()函数后,该函数会将基类中同名函数隐藏起来。main()函数代码仍与例1中内容相同,再次运行程序,运行结果如图4所示。
图4 调用派生类breath()函数
从运行结果看出,派生类定义了自己的breath()函数后,将基类中的同名函数隐藏,二义性消失。
虽然覆盖函数避免了调用函数的二义性,但覆盖会产生新的问题,若main()函数被修改为以下内容:
int main()
{
Bird *p; //定义基类指针变量
WaterBird waterbird; //定义派生类对象
p = new Bird; //使指针p赋值为基类对象地址
p->breath(); //调用基类Bird的breath()函数
p = &waterbird; //指针指向派生类对象
p->breath(); //调用基类Bird的breath()函数
system("pause");
return 0;
}
程序运行结果如图5所示。
图5 基类指针调用breath()函数运行结果
上述代码定义了基类指针,从运行结果看,即使该指针指向派生类对象,也会调用基类Bird的breath()函数,不会调用派生类WaterBird的breath()函数,这就是覆盖会产生的问题,需要引入虚函数解决。
2、派生类中访问公有成员时产生二义性
多重继承中派生类有多个基类,多个基类又可能由同一个基类派生,则在派生类中访问公共基类成员时会出现二义性。
继续讨论WaterBird类与基类的关系。类Waterbird多重继承自基类Bird和Fish,类Bird和类Fish又共同继承自Animal类,Animal类中的数据成员在WaterBird类中将出现两份拷贝。接下来将这四个类之间的关系通过一个案例来进行讨论,如例2所示。
例2
1 #include <iostream>
2 using namespace std;
3
4 class Animal //定义类Animal
5 {
6 public:
7 //定义类Animal的构造函数
8 Animal(int age):m_nAge(age) { cout << "Animal constructor!" << endl; }
9 protected:
10 int m_nAge; //成员m_nAge记录动物年龄
11 };
12 class Bird:public Animal //定义类Bird继承自Animal
13 {
14 public:
15 Bird(int age, int fh):Animal(age) //定义类Bird的构造函数
16 {
17 cout << "Bird constructor!" << endl;
18 m_nFlightAltitude = fh;
19 }
20 //定义获取鸟飞行高度的函数
21 int get_flightaltitude() { return m_nFlightAltitude; }
22 private:
23 int m_nFlightAltitude;
24 };
25
26 class Fish:public Animal //定义类Fish继承自Animal
27 {
28 public:
29 Fish(int age, int speed):Animal(age) //定义类Fish的构造函数
30 {
31 cout << "Fish constructor!" << endl;
32 m_nSwimSpeed = speed;
33 }
34 //定义获取鱼游速的函数
35 int get_swimspeed() { return m_nSwimSpeed; }
36 private:
37 int m_nSwimSpeed;
38 };
39
40 class WaterBird:public Bird, public Fish //定义水鸟类
41 {
42 public:
43 //定义水鸟类带参数的构造函数
44 WaterBird(int b_age, int f_age, int fh, int speed):Bird(b_age, fh),
45 Fish(f_age, speed)
46 {
47 cout << "WaterBird constructor!" << endl;
48 }
49 void print_animalage() //定义打印动物年龄的函数
50 {
51 cout << "age = " << Bird::m_nAge << endl;
52 cout << "age = " << Fish::m_nAge << endl;
53 }
54 };
55
56 int main()
57 {
58 WaterBird waterbird(5, 6, 20, 30); //定义水鸟对象
59
60 cout << "waterbird flight altitude: " << waterbird.get_flightaltitude()
61 << ", swimming speed:" << waterbird.get_swimspeed() << endl;
62
63 waterbird.print_animalage();
64 system("pause");
65 return 0;
66 }
程序的运行结果如图6所示。
图6 例2运行结果
例2中Animal类中存在保护数据成员m_nAge,派生类Bird、Fish中会各自存在m_nAge成员的一个拷贝,当WaterBird多重继承了Bird、Fish后,这两个基类中的m_nAg成员在WaterBird中会出现两份拷贝。类的继承关系如图7所示。
图7 公共基类的成员在派生类中产生两个拷贝
例2的运行结果显示,公共基类Animal中的m_nAge成员在派生类waterbird对象中有两个拷贝,打印了两个不同的值,而且公共基类Animal的构造函数被调用了两次,为了使这样的公共基类数据成员在派生类中只产生一个拷贝,需要将基类说明为虚基类,虚基类的内容在下节详细介绍。