当多重继承(multiple inheritance;MI)进入设计景框,程序有可能从一个以上的base classes继承相同名称(如函数、typedef等等)。那会导致较多的歧义机会。例如:
class BorrowableItem { // 图书馆允许你借某些东西
public:
void checkOut(); // 离开时进行检查
...
};
class ElectronicGadget {
private:
bool checkOut() const; // 指向自我检测,返回是否测试成功
...
};
class MP3Player: // 注意这里的多重继承(某些图书馆愿意借出MP3播放器)
public BorrowableItem,
public ElectronicGadget
{
... // 这里,class的定义不是我们关心重点
};
MP3Player mp;
mp.checkOut(); // 歧义!调用的是哪个checkOut?
注意此例之中对checkOut的调用是歧义(模棱两可)的,即使两个函数之中只有一个可取用(BorrowableItem内的checkOut是public,ElectronicGadget内的却是private)。这与C++用来解析(resolving)重载函数的调用的规则相符:在看到是否有个函数可取用之前,C++首先确认这个函数对此调用之言是最佳匹配。找出最佳匹配函数后才检验其可取用性。本例的两个checkOuts有相同的匹配程度(译注:因此才造成歧义),没有所谓最佳匹配。因此ElectronicGadget::checkOut的可取用性也就从未被编译器审查。
为了解决这个歧义,你必须明白指出你要调用哪一个base class内的函数:
mp.BorrowableItem::checkOut(); // 哎呀,原来是这个checkOut...
你当然也可以尝试明确调用ElectronicGadget::checkOut,但然后你会获得一个“尝试调用private成员函数”的错误。
多重继承的意思是继承一个以上的base classes,但这些base classes并不常在继承体系中又有更高级的base classes,因为那会导致要命的“钻石型多重继承”:
任何时候如果你有一个继承体系而其中某个base class和某个derived class之间有一条以上的相通路线(就像上述的File和IOFile之间有两条路径,分别穿越InputFile和OutputFile),你就必须面对这样一个问题:是否打算让base class内的成员变量经由每一条路径被复制?假设File class有个成员变量fileName,那么IOFile内该有多少笔这个名称的数据呢?从某个角度说,IOFile从其每一个base class继承一份,所以其对象内应该有两份fileName成员变量。但从另一个角度说,简单的逻辑告诉我们,IOFile对象只该有一个文件名称,所以它继承自两个base classes而来的fileName不该重复。
C++其缺省做法是执行复制(也就是上一段所说的第一个做法)。如果那不是你要的,你必须令那个带有此数据的class(也就是File)成为一个virtual base class。为了这样做,你必须令所有直接继承自它的classes采用“virtual继承”:
C++标准程序库内含一个多重继承体系,其结构就如右图那样,只不过其classes其实是class templates,名称分别是basic_ios,basic_istream,baseic_ostream和basic_iostream,而非这里的File,InputFile,OutputFile和IOFile。
virtual继承的classes所产生的对象往往比使用non-virtual继承的体积大,访问virtual base classes的成员变量时,也比访问non-virtual base classes的成员变量速度慢。种种细节因编译器不同而异,但基本重点很清楚:你得为virtual继承付出代价。
virtual继承的成本还包括其他方面。支配“virtual base classes初始化”的规则比起non-virtual bases的情况远为复杂且不直观。
我对virtual base classes(亦相当于对virtual继承)的忠告很简单。第一,非必要不使用virtual bases。平常请使用non-virtual继承。第二,如果你必须使用virtual base classes,尽可能避免在其中放置数据。这么一来你就不需担心这些classes身上的初始化(和赋值)所带来的诡异事情了。Java和.NET的Interfaces值得注意,它在许多方面兼容于C++的virtual base classes,而且也不允许含有任何数据。
请记住
- 多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要。
- virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具有实用价值的情况。
- 多重继承的确有正当用途。其中一个情节涉及“public继承某个Interface class”和“private继承某个协助实现的class”的两相组合。