我们前面提到过,多重继承常被认为是面向对象编程中复杂且没有必要的部分。这就仁者见仁,智者见智了,留给大家去评判。本节解释c++中的多重继承。
1、多个类继承
从语法角度来说,定义一个有多个父类的类是很简单的。需要做的就是当声明类名的时候把基类一个个地列出来。
class Baz : public Foo, public Bar { /* Etc. */ };
通过列出多个父类,Baz对象有下列特点:
- Baz对象支持Foo与Bar的公共成员函数,包含了两者的数据成员。
- Baz类的成员函数可以访问Foo与Bar的受保护的数据与成员函数。
- Baz对象可以向上转化为Foo或Bar。
- 生成新的Baz对象自动调用Foo与Bar的缺省构造函数,以在类定义中列出的类的顺序。
- 删除Baz对象自动调用Foo与Bar类的析构函数,以类定义中类列出的相反的顺序。
下面的例子展示了一个类,DogBird,它有两个父类,一个Dog类和一个Bird类,如下图所示:
事实是dog-bird是一个很搞笑的例子,不应该被看作多重继承自身就很离奇。实话实说,留给大家去评说吧。
class Dog
{
public:virtual void bark() { println("Woof!"); }
};class Bird
{
public:virtual void chirp() { println("Chirp!"); }
};class DogBird : public Dog, public Bird
{
};
使用带有多重父类的类对象与没有多重父类的对象没有区别。实际上,客户端代码甚至都不需要知道类是否有两个父类。重要的是类支持的属性与行为,DogBird对象支持所有的Dog与Bird的公共成员函数。
DogBird myConfusedAnimal;myConfusedAnimal.bark();myConfusedAnimal.chirp();
该程序的输出如下:
Woof!
Chirp!
2、名字冲突与不明确的基类
构造一个多重继承看起来要崩溃的场景还是不难的。下面的例子展示一个必须要考虑的边缘场景。
2.1、名字不明确
如果Dog类与Bird类两者都有一个成员函数叫做eat()会出现什么情况?因为Dog与Bird真不不相关,其中的一个成员函数的版本不会重载另一个--它们都会在DogBird的继承类中继续存在。
只要客户端代码永远不会尝试去调用eat()成员函数,那就不是问题。DogBird类在存在两个版本的eat()的情况下也能正确地编译。然而,如果客户端代码尝试调用DogBird上的eat()成员函数,编译器就会给出错误,提示对eat()的调用是不明确的。编译器不知道去调用哪个版本。下面的代码引发了这种不确定性的错误:
class Dog
{
public:virtual void bark() { println("Woof!"); }virtual void eat() { println("The dog ate."); }
};class Bird
{
public:virtual void chirp() { println("Chirp!"); }virtual void eat() { println("The bird ate."); }
};class DogBird : public Dog, public Bird
{
};int main()
{DogBird myConfusedAnimal;myConfusedAnimal.eat(); // Error! Ambiguous call to member function eat()
}
如果注释掉main()中的调用eat()的最后一行,代码编译成功。
这种不明确的的解决方案是要么显式地使用dynamic_cast()向上将对象进行转化,有效地对编译器隐藏不想要的成员函数的版本,要么使用非不明确的语法。例如,下面的代码显示了两种调用Dog版本的eat()的方法:
dynamic_cast<Dog&>(myConfusedAnimal).eat(); // Calls Dog::eat()myConfusedAnimal.Dog::eat(); // Calls Dog::eat()
继承类自身的成员函数也要显式地去除同名的不同成员函数的不明确性,通过使用用于访问父成员函数的同样的语法,也就是,::范围解析符。例如,DogBird类可以防止不明确的错误在其它代码中,通过定义自身的eat()成员函数。它会决定调用哪个父版本。
class DogBird : public Dog, public Bird
{
public:void eat() override{Dog::eat(); // Explicitly call Dog's version of eat()}};
另一个防止不明确的错误的方式是使用using声明来显式地指出在DogBird中继承的是哪个版本的eat()。举例如下:
class DogBird : public Dog, public Bird
{
public:using Dog::eat; // Explicitly inherit Dog's version of eat()
};