触及 multiple inheritance (MI)(多继续)的时候,C++ 社区就会鲜明地分裂为两个基本的阵营。一个阵营认为假如 single inheritance (SI)(单继续)是有好处的,multiple inheritance(多继续)一定更有好处。另一个阵营认为 single inheritance(单继续)有好处,但是多继续引起的麻烦使它得不偿失。在本文中,我们的主要目的是理解在 MI 问题上的这两种看法。
首要的事情之一是要承认当将 MI 引入设计领域时,就有可能从多于一个的 base class(基类)中继续相同的名字(例如,函数,typedef,等等)。这就为歧义性提供了新的时机。例如:
class BorrowableItem { // something a library lets you borrow public: void checkOut(); // check the item out from the library .. };
class ElectronicGadget { PRivate: bool checkOut() const; // perform self-test, return whether ... // test sUCceeds };
class mp3Player: // note MI here public BorrowableItem, // (some libraries loan MP3 players) public ElectronicGadget { ... }; // class definition is unimportant
mp.BorrowableItem::checkOut(); // ah, that checkOut... 当然,你也可以尝试显式调用 ElectronicGadget::checkOut,但这样做会有一个 "you're trying to call a private member function"(你试图调用一个私有成员函数)错误代替歧义性错误。
multiple inheritance(多继续)仅仅意味着从多于一个的 base class(基类)继续,但是在还有 higher-level base classes(更高层次基类)的 hierarchies(继续体系)中出现 MI 也并不罕见。这会导致有时被称为 "deadly MI diamond"(致命的多继续菱形)的后果。
class File { ... }; class InputFile: public File { ... }; class OutputFile: public File { ... }; class IOFile: public InputFile, public OutputFile { ... };
在一个“在一个 base class(基类)和一个 derived class(派生类)之间有多于一条路径的 inheritance hierarchy(继续体系)”(就像上面在 File 和 IOFile 之间,有通过 InputFile 和 OutputFile 的两条路径)的任何时候,你都必须面对是否需要为每一条路径复制 base class(基类)中的 data members(数据成员)的问题。例如,假设 File class 有一个 data members(数据成员)fileName。IOFile 中应该有这个 field(字段)的多少个拷贝呢?一方面,它从它的每一个 base classes(基类)继续一个拷贝,这就暗示 IOFile 应该有两个 fileName data members(数据成员)。另一方面,简单的逻辑告诉我们一个 IOFile object(对象)应该仅有一个 file name(文件名),所以通过它的两个 base classes(基类)继续来的 fileName field(字段)不应该被复制。
C++ 在这个争议上没有自己的立场。它恰当地支持两种选项,虽然它的缺省方式是执行复制。假如那不是你想要的,你必须让这个 class(类)带有一个 virtual base class(虚拟基类)的数据(也就是 File)。为了做到这一点,你要让从它直接继续的所有的 classes(类)使用 virtual inheritance(虚拟继续):
class File { ... }; class InputFile: virtual public File { ... }; class OutputFile: virtual public File { ... }; class IOFile: public InputFile, public OutputFile { ... }; 标准 C++ 库包含一个和此类似的 MI hierarchy(继续体系),只是那个 classes(类)是 class templates(类模板),名字是 basic_ios,basic_istream,basic_ostream 和 basic_iostream,而不是 File,InputFile,OutputFile 和 IOFile。
从正确行为的观点看,public inheritance(公有继续)应该总是 virtual(虚拟)的。假如这是唯一的观点,规则就变得简单了:你使用 public inheritance(公有继续)的任何时候,都使用 virtual public inheritance(虚拟公有继续)。唉,正确性不是唯一的视角。避免 inherited fields(继续来的字段)复制需要在编译器的一部分做一些 behind-the-scenes legerdemain(幕后的戏法),而结果是从使用 virtual inheritance(虚拟继续)的 classes(类)创建的 objects(对象)通常比不使用 virtual inheritance(虚拟继续)的要大。访问 virtual base classes(虚拟基类)中的 data members(数据成员)也比那些 non-virtual base classes(非虚拟基类)中的要慢。编译器与编译器之间有一些细节不同,但基本的要点很清楚:virtual inheritance costs(虚拟继续要付出成本)。
它也有一些其它方面的成本。支配 initialization of virtual base classes(虚拟基类初始化)的规则比 non-virtual bases(非虚拟基类)的更加复杂而且更不直观。初始化一个 virtual base(虚拟基)的职责由 hierarchy(继续体系)中 most derived class(层次最低的派生类)承担。这个规则中包括的含义:
// factory function to create a Person object from a unique database ID; // see Item 18 for why the return type isn't a raw pointer std::tr1::shared_ptr<IPerson> makePerson(DatabaseID personIdentifier);
// function to get a database ID from the user DatabaseID askUserForDatabaseID();
DatabaseID id(askUserForDatabaseID()); std::tr1::shared_ptr<IPerson> pp(makePerson(id)); // create an object // supporting the // IPerson interface
const char * PersonInfo::theName() const { // reserve buffer for return value; because this is // static, it's automatically initialized to all zeros static char value[Max_Formatted_Field_Value_Length];
但是 CPerson 还必须实现 IPerson interface(接口),而这被称为 public inheritance(公有继续)。这就引出一个 multiple inheritance(多继续)的合理应用:组合 public inheritance of an interface(一个接口的公有继续)和 private inheritance of an implementation(一个实现的私有继续):
class IPerson { // this class specifies the public: // interface to be implemented virtual ~IPerson();
class DatabaseID { ... }; // used below; details are // unimportant
class PersonInfo { // this class has functions public: // useful in implementing explicit PersonInfo(DatabaseID pid); // the IPerson interface virtual ~PersonInfo();
class CPerson: public IPerson, private PersonInfo { // note use of MI public: explicit CPerson( DatabaseID pid): PersonInfo(pid) {} virtual std::string name() const // implementations { return PersonInfo::theName(); } // of the required // IPerson member virtual std::string birthDate() const // functions { return PersonInfo::theBirthDate(); } private: // redefinitions of const char * valueDelimOpen() const { return ""; } // inherited virtual const char * valueDelimClose() const { return ""; } // delimiter }; // functions 在 UML 中,这个设计看起来像这样:
这个例子证实 MI 既是有用的,也是可理解的。
时至今日,multiple inheritance(多继续)不过是 object-oriented toolbox(面向对象工具箱)里的又一种工具而已,典型情况下,它的使用和理解更加复杂,所以假如你得到一个或多或少等同于一个 MI 设计的 SI 设计,则 SI 设计总是更加可取。假如你能拿出来的仅有的设计包含 MI,你应该更加专心地考虑一下——总会有一些方法使得 SI 也能做到。但同时,MI 有时是最清楚的,最易于维护的,最合理的完成工作的方法。在这种情况下,毫不畏惧地使用它。只是要确保谨慎地使用它。
Things to Remember
·multiple inheritance(多继续)比 single inheritance(单继续)更复杂。它能导致新的歧义问题和对 virtual inheritance(虚拟继续)的需要。
·virtual inheritance(虚拟继续)增加了 size(大小)和 speed(速度)成本,以及 initialization(初始化)和 assignment(赋值)的复杂度。当 virtual base classes(虚拟基类)没有数据时它是最适用的。
·multiple inheritance(多继续)有合理的用途。一种方案涉及组合从一个 Interface class(接口类)的 public inheritance(公有继续)和从一个有助于实现的 class(类)的 private inheritance(私有继续)。