欧美极品高清xxxxhd,国产日产欧美最新,无码AV国产东京热AV无码,国产精品人与动性XXX,国产传媒亚洲综合一区二区,四库影院永久国产精品,毛片免费免费高清视频,福利所导航夜趣136

標(biāo)題: 謹(jǐn)慎使用多繼承 [打印本頁(yè)]

作者: gaonan132    時(shí)間: 2018-3-25 09:47
標(biāo)題: 謹(jǐn)慎使用多繼承
 觸及 multiple inheritance (MI)(多繼承)的時(shí)候,C++ 社區(qū)就會(huì)鮮明地分裂為兩個(gè)基本的陣營(yíng)。一個(gè)陣營(yíng)認(rèn)為如果 single inheritance (SI)(單繼承)是有好處的,multiple inheritance(多繼承)一定更有好處。另一個(gè)陣營(yíng)認(rèn)為 single inheritance(單繼承)有好處,但是多繼承引起的麻煩使它得不償失。在本文中,我們的主要目的是理解在 MI 問(wèn)題上的這兩種看法。

  首要的事情之一是要承認(rèn)當(dāng)將 MI 引入設(shè)計(jì)領(lǐng)域時(shí),就有可能從多于一個(gè)的 base class(基類(lèi))中繼承相同的名字(例如,函數(shù),typedef,等等)。這就為歧義性提供了新的時(shí)機(jī)。例如:


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

MP3Player mp;

mp.checkOut(); // ambiguous! which checkOut?

  注意這個(gè)例子,即使兩個(gè)函數(shù)中只有一個(gè)是可訪問(wèn)的,對(duì) checkOut 的調(diào)用也是有歧義的。(checkOut 在 BorrowableItem 中是 public(公有)的,但在 ElectronicGadget 中是 private(私有)的。)這與 C++ 解析 overloaded functions(重載函數(shù))調(diào)用的規(guī)則是一致的:在看到一個(gè)函數(shù)的是否可訪問(wèn)之前,C++ 首先確定與調(diào)用匹配最好的那個(gè)函數(shù)。只有在確定了 best-match function(最佳匹配函數(shù))之后,才檢查可訪問(wèn)性。這目前的情況下,兩個(gè) checkOuts 具有相同的匹配程度,所以就不存在最佳匹配。因此永遠(yuǎn)也不會(huì)檢查到 ElectronicGadget::checkOut 的可訪問(wèn)性。

  為了消除歧義性,你必須指定哪一個(gè) base class(基類(lèi))的函數(shù)被調(diào)用:


mp.BorrowableItem::checkOut(); // ah, that checkOut...

  當(dāng)然,你也可以嘗試顯式調(diào)用 ElectronicGadget::checkOut,但這樣做會(huì)有一個(gè) "you're trying to call a private member function"(你試圖調(diào)用一個(gè)私有成員函數(shù))錯(cuò)誤代替歧義性錯(cuò)誤。

  multiple inheritance(多繼承)僅僅意味著從多于一個(gè)的 base class(基類(lèi))繼承,但是在還有 higher-level base classes(更高層次基類(lèi))的 hierarchies(繼承體系)中出現(xiàn) MI 也并不罕見(jiàn)。這會(huì)導(dǎo)致有時(shí)被稱(chēng)為 "deadly MI diamond"(致命的多繼承菱形)的后果。


class File { ... };
class InputFile: public File { ... };
class OutputFile: public File { ... };
class IOFile: public InputFile,
public OutputFile
{ ... };


  在一個(gè)“在一個(gè) base class(基類(lèi))和一個(gè) derived class(派生類(lèi))之間有多于一條路徑的 inheritance hierarchy(繼承體系)”(就像上面在 File 和 IOFile 之間,有通過(guò) InputFile 和 OutputFile 的兩條路徑)的任何時(shí)候,你都必須面對(duì)是否需要為每一條路徑復(fù)制 base class(基類(lèi))中的 data members(數(shù)據(jù)成員)的問(wèn)題。例如,假設(shè) File class 有一個(gè) data members(數(shù)據(jù)成員)fileName。IOFile 中應(yīng)該有這個(gè) field(字段)的多少個(gè)拷貝呢?一方面,它從它的每一個(gè) base classes(基類(lèi))繼承一個(gè)拷貝,這就暗示 IOFile 應(yīng)該有兩個(gè) fileName data members(數(shù)據(jù)成員)。另一方面,簡(jiǎn)單的邏輯告訴我們一個(gè) IOFile object(對(duì)象)應(yīng)該僅有一個(gè) file name(文件名),所以通過(guò)它的兩個(gè) base classes(基類(lèi))繼承來(lái)的 fileName field(字段)不應(yīng)該被復(fù)制。

  C++ 在這個(gè)爭(zhēng)議上沒(méi)有自己的立場(chǎng)。它恰當(dāng)?shù)刂С謨煞N選項(xiàng),雖然它的缺省方式是執(zhí)行復(fù)制。如果那不是你想要的,你必須讓這個(gè) class(類(lèi))帶有一個(gè) virtual base class(虛擬基類(lèi))的數(shù)據(jù)(也就是 File)。為了做到這一點(diǎn),你要讓從它直接繼承的所有的 classes(類(lèi))使用 virtual inheritance(虛擬繼承):


class File { ... };
class InputFile: virtual public File { ... };
class OutputFile: virtual public File { ... };
class IOFile: public InputFile,
public OutputFile
{ ... };

  標(biāo)準(zhǔn) C++ 庫(kù)包含一個(gè)和此類(lèi)似的 MI hierarchy(繼承體系),只是那個(gè) classes(類(lèi))是 class templates(類(lèi)模板),名字是 basic_ios,basic_istream,basic_ostream 和 basic_iostream,而不是 File,InputFile,OutputFile 和 IOFile。

  從正確行為的觀點(diǎn)看,public inheritance(公有繼承)應(yīng)該總是 virtual(虛擬)的。如果這是唯一的觀點(diǎn),規(guī)則就變得簡(jiǎn)單了:你使用 public inheritance(公有繼承)的任何時(shí)候,都使用 virtual public inheritance(虛擬公有繼承)。唉,正確性不是唯一的視角。避免 inherited fields(繼承來(lái)的字段)復(fù)制需要在編譯器的一部分做一些 behind-the-scenes legerdemain(幕后的戲法),而結(jié)果是從使用 virtual inheritance(虛擬繼承)的 classes(類(lèi))創(chuàng)建的 objects(對(duì)象)通常比不使用 virtual inheritance(虛擬繼承)的要大。訪問(wèn) virtual base classes(虛擬基類(lèi))中的 data members(數(shù)據(jù)成員)也比那些 non-virtual base classes(非虛擬基類(lèi))中的要慢。編譯器與編譯器之間有一些細(xì)節(jié)不同,但基本的要點(diǎn)很清楚:virtual inheritance costs(虛擬繼承要付出成本)。

  它也有一些其它方面的成本。支配 initialization of virtual base classes(虛擬基類(lèi)初始化)的規(guī)則比 non-virtual bases(非虛擬基類(lèi))的更加復(fù)雜而且更不直觀。初始化一個(gè) virtual base(虛擬基)的職責(zé)由 hierarchy(繼承體系)中 most derived class(層次最低的派生類(lèi))承擔(dān)。這個(gè)規(guī)則中包括的含義:

 。1) 從需要 initialization(初始化)的 virtual bases(虛擬基)派生的 classes(類(lèi))必須知道它們的 virtual bases(虛擬基),無(wú)論它距離那個(gè) bases(基)有多遠(yuǎn);

 。2) 當(dāng)一個(gè)新的 derived class(派生類(lèi))被加入繼承體系時(shí),它必須為它的 virtual bases(虛擬基)(包括直接的和間接的)承擔(dān) initialization responsibilities(初始化職責(zé))。

  我對(duì)于 virtual base classes(虛擬基類(lèi))(也就是 virtual inheritance(虛擬繼承))的建議很簡(jiǎn)單。首先,除非必需,否則不要使用 virtual bases(虛擬基)。缺省情況下,使用 non-virtual inheritance(非虛擬繼承)。第二,如果你必須使用 virtual base classes(虛擬基類(lèi)),試著避免在其中放置數(shù)據(jù)。這樣你就不必在意它的 initialization(初始化)(以及它的 turns out(清空),assignment(賦值))規(guī)則中的一些怪癖。值得一提的是 Java 和 .NET 中的 Interfaces(接口)不允許包含任何數(shù)據(jù),它們?cè)诤芏喾矫婵梢院?C++ 中的 virtual base classes(虛擬基類(lèi))相比照。

  現(xiàn)在我們使用下面的 C++ Interface class(接口類(lèi))(參見(jiàn)《C++箴言:最小化文件之間的編譯依賴》)來(lái)為 persons(人)建模:


class IPerson {
public:
 virtual ~IPerson();

 virtual std::string name() const = 0;
 virtual std::string birthDate() const = 0;
};

  IPerson 的客戶只能使用 IPerson 的 pointers(指針)和 references(引用)進(jìn)行編程,因?yàn)?abstract classes(抽象類(lèi))不能被實(shí)例化。為了創(chuàng)建能被當(dāng)作 IPerson objects(對(duì)象)使用的 objects(對(duì)象),IPerson 的客戶使用 factory functions(工廠函數(shù))(再次參見(jiàn) Item 31)instantiate(實(shí)例化)從 IPerson 派生的 concrete classes(具體類(lèi)):


// 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

... // manipulate *pp via
// IPerson's member
// functions

  但是 makePerson 怎樣創(chuàng)建它返回的 pointers(指針)所指向的 objects(對(duì)象)呢?顯然,必須有一些 makePerson 可以實(shí)例化的從 IPerson 派生的 concrete class(具體類(lèi))。

  假設(shè)這個(gè) class(類(lèi))叫做 CPerson。作為一個(gè) concrete class(具體類(lèi)),CPerson 必須提供它從 IPerson 繼承來(lái)的 pure virtual functions(純虛擬函數(shù))的 implementations(實(shí)現(xiàn))。它可以從頭開(kāi)始寫(xiě),但利用包含大多數(shù)或全部必需品的現(xiàn)有組件更好一些。例如,假設(shè)一個(gè)老式的 database-specific class(老式的數(shù)據(jù)庫(kù)專(zhuān)用類(lèi))PersonInfo 提供了 CPerson 所需要的基本要素:


class PersonInfo {
public:
 explicit PersonInfo(DatabaseID pid);
 virtual ~PersonInfo();

 virtual const char * theName() const;
 virtual const char * theBirthDate() const;
 ...

private:
 virtual const char * valueDelimOpen() const; // see
 virtual const char * valueDelimClose() const; // below
 ...
};

  你可以看出這是一個(gè)老式的 class(類(lèi)),因?yàn)?member functions(成員函數(shù))返回 const char*s 而不是 string objects(對(duì)象)。盡管如此,如果鞋子合適,為什么不穿呢?這個(gè) class(類(lèi))的 member functions(成員函數(shù))的名字暗示結(jié)果很可能會(huì)非常合適。

  你突然發(fā)現(xiàn) PersonInfo 是設(shè)計(jì)用來(lái)幫助以不同的格式打印 database fields(數(shù)據(jù)庫(kù)字段)的,每一個(gè)字段的值的開(kāi)始和結(jié)尾通過(guò)指定的字符串定界。缺省情況下,字段值開(kāi)始和結(jié)尾定界符是方括號(hào),所以字段值 "Ring-tailed Lemur" 很可能被安排成這種格式:


[Ring-tailed Lemur]

  根據(jù)方括號(hào)并非滿足 PersonInfo 的全體客戶的期望的事實(shí),virtual functions(虛擬函數(shù))valueDelimOpen 和 valueDelimClose 允許 derived classes(派生類(lèi))指定它們自己的開(kāi)始和結(jié)尾定界字符串。PersonInfo 的 member functions(成員函數(shù))的 implementations(實(shí)現(xiàn))調(diào)用這些 virtual functions(虛擬函數(shù))在它們返回的值上加上適當(dāng)?shù)亩ń绶W鳛橐粋(gè)例子使用 PersonInfo::theName,代碼如下:


const char * PersonInfo::valueDelimOpen() const
{
 return "["; // default opening delimiter
}

const char * PersonInfo::valueDelimClose() const
{
 return "]"; // default closing delimiter
}

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];

 // write opening delimiter
 std::strcpy(value, valueDelimOpen());

 append to the string in value this object's name field (being careful to avoid buffer overruns!)

 // write closing delimiter
 std::strcat(value, valueDelimClose());

 return value;
}

  有人可能會(huì)質(zhì)疑 PersonInfo::theName 的陳舊的設(shè)計(jì)(特別是一個(gè) fixed-size static buffer(固定大小靜態(tài)緩沖區(qū))的使用,這樣的東西發(fā)生 overrun(越界)和 threading(線程)問(wèn)題是比較普遍的——參見(jiàn)《C++箴言:必須返回對(duì)象時(shí)別返回引用》),但是請(qǐng)把這樣的問(wèn)題放到一邊而注意這里:theName 調(diào)用 valueDelimOpen 生成它要返回的 string(字符串)的開(kāi)始定界符,然后它生成名字值本身,然后它調(diào)用 valueDelimClose。

  因?yàn)?valueDelimOpen 和 valueDelimClose 是 virtual functions(虛擬函數(shù)),theName 返回的結(jié)果不僅依賴于 PersonInfo,也依賴于從 PersonInfo 派生的 classes(類(lèi))。

  對(duì)于 CPerson 的實(shí)現(xiàn)者,這是好消息,因?yàn)楫?dāng)細(xì)讀 IPerson documentation(文檔)中的 fine print(晦澀的條文)時(shí),你發(fā)現(xiàn) name 和 birthDate 需要返回未經(jīng)修飾的值,也就是,不允許有定界符。換句話說(shuō),如果一個(gè)人的名字叫 Homer,對(duì)那個(gè)人的 name 函數(shù)的一次調(diào)用應(yīng)該返回 "Homer",而不是 "[Homer]"。

  CPerson 和 PersonInfo 之間的關(guān)系是 PersonInfo 碰巧有一些函數(shù)使得 CPerson 更容易實(shí)現(xiàn)。這就是全部。因而它們的關(guān)系就是 is-implemented-in-terms-of,而我們知道有兩種方法可以表現(xiàn)這一點(diǎn):經(jīng)由 composition(復(fù)合)(參見(jiàn)《C++箴言:通過(guò)composition模擬“has-a”》)和經(jīng)由 private inheritance(私有繼承)(參見(jiàn)《C++箴言:謹(jǐn)慎使用私有繼承》)!C++箴言:謹(jǐn)慎使用私有繼承》指出 composition(復(fù)合)是通常的首選方法,但如果 virtual functions(虛擬函數(shù))要被重定義,inheritance(繼承)就是必不可少的。在當(dāng)前情況下,CPerson 需要重定義 valueDelimOpen 和 valueDelimClose,所以簡(jiǎn)單的 composition(復(fù)合)做不到。最直截了當(dāng)?shù)慕鉀Q方案是讓 CPerson 從 PersonInfo privately inherit(私有繼承),雖然 《C++箴言:謹(jǐn)慎使用私有繼承》說(shuō)過(guò)只要多做一點(diǎn)工作,則 CPerson 也能用 composition(復(fù)合)和 inheritance(繼承)的組合有效地重定義 PersonInfo 的 virtuals(虛擬函數(shù))。這里,我們用 private inheritance(私有繼承)。

  但是 CPerson 還必須實(shí)現(xiàn) IPerson interface(接口),而這被稱(chēng)為 public inheritance(公有繼承)。這就引出一個(gè) multiple inheritance(多繼承)的合理應(yīng)用:組合 public inheritance of an interface(一個(gè)接口的公有繼承)和 private inheritance of an implementation(一個(gè)實(shí)現(xiàn)的私有繼承):


class IPerson { // this class specifies the
public: // interface to be implemented
 virtual ~IPerson();

 virtual std::string name() const = 0;
 virtual std::string birthDate() const = 0;
};

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();

 virtual const char * theName() const;
 virtual const char * theBirthDate() const;

 virtual const char * valueDelimOpen() const;
 virtual const char * valueDelimClose() const;
 ...
};

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 中,這個(gè)設(shè)計(jì)看起來(lái)像這樣:



  這個(gè)例子證明 MI 既是有用的,也是可理解的。

  時(shí)至今日,multiple inheritance(多繼承)不過(guò)是 object-oriented toolbox(面向?qū)ο蠊ぞ呦洌├锏挠忠环N工具而已,典型情況下,它的使用和理解更加復(fù)雜,所以如果你得到一個(gè)或多或少等同于一個(gè) MI 設(shè)計(jì)的 SI 設(shè)計(jì),則 SI 設(shè)計(jì)總是更加可取。如果你能拿出來(lái)的僅有的設(shè)計(jì)包含 MI,你應(yīng)該更加用心地考慮一下——總會(huì)有一些方法使得 SI 也能做到。但同時(shí),MI 有時(shí)是最清晰的,最易于維護(hù)的,最合理的完成工作的方法。在這種情況下,毫不畏懼地使用它。只是要確保謹(jǐn)慎地使用它。

  Things to Remember

  ·multiple inheritance(多繼承)比 single inheritance(單繼承)更復(fù)雜。它能導(dǎo)致新的歧義問(wèn)題和對(duì) virtual inheritance(虛擬繼承)的需要。

  ·virtual inheritance(虛擬繼承)增加了 size(大小)和 speed(速度)成本,以及 initialization(初始化)和 assignment(賦值)的復(fù)雜度。當(dāng) virtual base classes(虛擬基類(lèi))沒(méi)有數(shù)據(jù)時(shí)它是最適用的。

  ·multiple inheritance(多繼承)有合理的用途。一種方案涉及組合從一個(gè) Interface class(接口類(lèi))的 public inheritance(公有繼承)和從一個(gè)有助于實(shí)現(xiàn)的 class(類(lèi))的 private inheritance(私有繼承)。





歡迎光臨 (http://m.raoushi.com/bbs/) Powered by Discuz! X3.1