觸及 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(私有繼續)。