條款 40:明智而審慎地使用多重繼承

Effective C++ 中文版 第三版》讀書(shū)筆記

** 條款 40:明智而審慎地使用多重繼承 **

一旦涉及多重繼承 (multiple inheritance涎跨;MI):

程序有可能從一個(gè)以上的 base class 繼承相同名稱(如函數(shù)、typedef 等)窿侈。那會(huì)導(dǎo)致較多的歧義機(jī)會(huì)泪勒。例如:

class BorrowableItem { 
public: 
    void checkOut(); 
};

class ElectronicGadet { 
private: 
    bool checkOut() const; 
};

class MP3Player: public BorrowableItem 

public ElectronicGadet 
{...}; 

MP3Player mp; 

mp.checkOut();//歧義田藐,調(diào)用的是哪個(gè)checkOut潜秋?

即使兩個(gè)之中只有一個(gè)可取用(ElectronicGadet 是 private)恨溜。這與 C++ 用來(lái)解析重載函數(shù)調(diào)用的規(guī)則相符:在看到是否有個(gè)函數(shù)可取之前垃杖,C++ 首先確認(rèn)這個(gè)函數(shù)對(duì)此調(diào)用之言是最佳匹配男杈。找出最佳匹配才檢驗(yàn)其可取用性。本例的兩個(gè) checkOut 有相同的匹配程度缩滨。沒(méi)有所謂最佳匹配势就。因此 ElectronicGadget::checkOut 的可取用性就從未被編譯器審查。

為了解決這個(gè)歧義脉漏,必須明白指出你要調(diào)用哪個(gè) base class 內(nèi)的函數(shù):

mp.BorrowableItem::checkOut();

你當(dāng)然也可以明確調(diào)用 ElectronicGadget::checkOut()苞冯,但然后你會(huì)獲得一個(gè) “嘗試調(diào)用 private 成員函數(shù)” 的錯(cuò)誤。

當(dāng)即稱一個(gè)以上的 base classes侧巨,這些 base classes 并不常在繼承體系中有更高級(jí)的 base classes舅锄,因?yàn)槟菚?huì)導(dǎo)致要命的 “鉆石型多重繼承”:

class File{...}; 

class InputFile: public File {...}; 

class OutputFile: public File{...}; 

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

任何時(shí)候只要你的繼承體系中某個(gè) base class 和某個(gè) derived class 之間有一條以上的想通路線,你就必須面對(duì)這樣一個(gè)問(wèn)題:是否打算讓 base class 內(nèi)的成員經(jīng)由每一條路徑被復(fù)制司忱?假設(shè) File 有個(gè)成員變量 fileName皇忿,那么 IOFile 應(yīng)給有兩份 fileName 成員變量。但從另一個(gè)角度來(lái)說(shuō)坦仍,簡(jiǎn)單的邏輯告訴我們鳍烁,IOFile 對(duì)象只有一個(gè)文件名稱,所以他繼承自兩個(gè) base class 而來(lái)的 fileName 不能重復(fù)繁扎。

C++ 的缺省做法是執(zhí)行重復(fù)幔荒。如果那不是你要的糊闽,你必須令那個(gè)帶有此數(shù)據(jù)的 base class(也就是 File)成為一個(gè) virtual base class。必須令所有直接繼承自它的 classes 采用 “virtual 繼承”:

class File{...}; 

class InputFile: virtual public File {...}; 

class OutputFile: virtual public File{...}; 

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

C++ 標(biāo)準(zhǔn)程序庫(kù)內(nèi)含一個(gè)多重繼承體系爹梁,只不過(guò)其 class 是 class template: basic_ios右犹,basic_istream,basic_ostream 和 basic_iostream姚垃。

從正確行為來(lái)看念链,public 繼承應(yīng)該總是 virtual。如果這是唯一一個(gè)觀點(diǎn)积糯,規(guī)則很簡(jiǎn)單:任何時(shí)候當(dāng)你使用 public 繼承掂墓,請(qǐng)改用 virtual public 繼承。但是看成,正確性并不是唯一觀點(diǎn)梆暮。為避免繼承來(lái)的成員變量重復(fù),編譯器必須提供若干幕后戲法绍昂,其后果就是:使用 virtual 繼承的那些 classes 所產(chǎn)生的對(duì)象往往比使用 non-virtual 繼承的兄弟們體積大啦粹,訪問(wèn) virtual base classes 的成員變量時(shí),也比訪問(wèn) non-virtual base classes 成員變量速度慢窘游。

virtual 繼承的成本還包括其他:支配 “virtual base classes 初始化” 的規(guī)則比起 non-virtual base 的情況遠(yuǎn)為復(fù)雜和不直觀唠椭。virtual base 的初始化責(zé)任是由繼承體系中的最底層(most derived)class 負(fù)責(zé),1忍饰、class 若派生自 virtual base class 而需要初始化贪嫂,必須認(rèn)知其 virtual bases —— 不論那些 bases 距離多遠(yuǎn),2艾蓝、當(dāng)一個(gè)新的 derived class 加入繼承體系中力崇,它必須承擔(dān)起 virtual bases(不論直接或間接)的初始化工作。

我們對(duì) virtual 繼承的忠告:第一赢织,非必要不要使用 virtual bases亮靴。第二,如果必須使用 virtual bases于置,盡可能避免在其中放置數(shù)據(jù)茧吊。這樣你就不需擔(dān)心這些 classes 身上的初始化(和賦值)所帶來(lái)的詭異事情了。

下面看看這個(gè) C++ Interface class:

class IPerson{ 
public: 
    virtual ~IPerson(); 
    virtual std::string name() const = 0; 
    virtual std::string birthDate() const =0; 
};

//factory function,根據(jù)一個(gè)獨(dú)一無(wú)二的數(shù)據(jù)庫(kù)ID創(chuàng)建一個(gè)Person對(duì)象 
std::tr1::shared_ptr<IPerson> makePerson(DatabaseID personIdentifier); 

DatabaseID askUserForDatabaseID(); 

DatabaseID id(askUserForDatabaseID()); 

std::tr1::shared_ptr<IPerson> pp(makePerson(id));

假設(shè)一個(gè)派生自 IPerson 的具象 class CPerson八毯,它必須提供 “繼承自 Iperson” 的 pure virtual 函數(shù)的實(shí)現(xiàn)代碼搓侄。我們可以寫(xiě)出這些,但更好的是利用既有組件话速。例如有個(gè)既有的數(shù)據(jù)庫(kù)相關(guān) class讶踪,PersonInfo:

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; 
    virtual const char* valueDelimClose() const; 
};

PersonInfo 被設(shè)計(jì)用來(lái)協(xié)助以各種格式打印數(shù)據(jù)庫(kù)字段,每個(gè)字段值的起始點(diǎn)和結(jié)束點(diǎn)以特殊字符串為界泊交。默認(rèn)為 “[”,“]”乳讥,但并非人人都愛(ài)方括號(hào)筹麸,所以提供兩個(gè) virtual 函數(shù) valueDelimOpen 和 ValueDelimClose 語(yǔ)序 derived class 設(shè)定他們自己的頭尾界限符號(hào)。PersonInfo 成員函數(shù)將調(diào)用這些 virtual 函數(shù)雏婶,把適當(dāng)?shù)慕缦薹?hào)添加到它們的返回值上。PersonInfo::theName 的代碼看起來(lái)像這樣:

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

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

const char* PersonInfo::theName() const 
{ 
    //保留緩沖區(qū)給返回值使用:static白指,自動(dòng)初始化為“全0” 
    static char value[Max_Formatted_Field_Value_Length]; 

    //寫(xiě)入起始符號(hào) 
    std::strcpy(value, valueDelimOpen()); 

    //將value內(nèi)的字符串附到這個(gè)對(duì)象的name成員變量中 

    //寫(xiě)入結(jié)尾符號(hào) 
    std::strcat(value, valueDelimClose()); 
    return value; 
}

所以 theName 返回的結(jié)果不僅僅取決于 PersonInfo 也取決于從 PersonInfo 派生下去的 classes留晚。

Cperson 和 personInfo 的關(guān)系是,PersonInfo 剛好有若干函數(shù)可幫助 Cperson 比較容易實(shí)現(xiàn)出來(lái)告嘲。因此它們的關(guān)系是 is-implemented-in-term-of错维。這種關(guān)系可以兩種技術(shù)實(shí)現(xiàn):復(fù)合和 private 繼承。一般復(fù)合必要受歡迎橄唬,本例之中 Cperson 要重新定義 valueDelimOpen 和 valueDelimClose赋焕,所以直接的解法是 private 繼承。

Cperson 還有必須實(shí)現(xiàn) Iperson 的接口仰楚,那得要 public 繼承才能完成隆判。這導(dǎo)致多重繼承的一個(gè)通情達(dá)理的應(yīng)用:將 “public 繼承自某接口” 和 “private 繼承自某實(shí)現(xiàn)” 結(jié)合在一起:

class Cperson: public IPerson, private PersonInfo{ 
public: 
    explicit Cperson(DatabaseID pid): PersonInfo(pid){} 

    virtual std::string name() const 
    { 
        return PersonInfo::theName(); 
    } 

    virtual std::string birthDate() const 
    { 
        return PersonInfo::theBirthDate(); 
    } 

private: 
    const char* valueDelimOpen() const {return "";} 
    const char* valueDelimClose() const {return "";} 
};

如果你唯一能提出的設(shè)計(jì)涉及多重繼承,你應(yīng)該再努力想一想 —— 幾乎可以說(shuō)一定會(huì)有某些方案讓單一繼承行的通僧界。然而有時(shí)候多繼承的確是完成任務(wù)最簡(jiǎn)潔侨嘀、最易維護(hù)、最合理的做法捂襟,就別害怕使用它咬腕。只是確定,的確在明智而審慎的情況下使用它葬荷。

請(qǐng)記渍枪病:

  1. 多重繼承比單一繼承復(fù)雜。它可能導(dǎo)致新的歧義性宠漩,以及對(duì) virtual 繼承的需求举反。

  2. virtual 繼承會(huì)增加大小、速度扒吁、初始化(及賦值)復(fù)雜度等等成本照筑。如果 virtual base class 不帶任何數(shù)據(jù),將是最具實(shí)用價(jià)值的情況瘦陈。

  3. 多重繼承的確有正當(dāng)用途凝危。其中一個(gè)情節(jié)涉及 “public 繼承某個(gè) Interface class” 和 “private 繼承某個(gè)協(xié)助實(shí)現(xiàn)的 class” 的兩相組合。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末晨逝,一起剝皮案震驚了整個(gè)濱河市蛾默,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捉貌,老刑警劉巖支鸡,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冬念,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡牧挣,警方通過(guò)查閱死者的電腦和手機(jī)急前,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瀑构,“玉大人裆针,你說(shuō)我怎么就攤上這事∷律危” “怎么了世吨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)呻征。 經(jīng)常有香客問(wèn)我耘婚,道長(zhǎng),這世上最難降的妖魔是什么陆赋? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任沐祷,我火速辦了婚禮,結(jié)果婚禮上攒岛,老公的妹妹穿的比我還像新娘戈轿。我一直安慰自己,他們只是感情好阵子,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布思杯。 她就那樣靜靜地躺著,像睡著了一般挠进。 火紅的嫁衣襯著肌膚如雪色乾。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天领突,我揣著相機(jī)與錄音暖璧,去河邊找鬼。 笑死君旦,一個(gè)胖子當(dāng)著我的面吹牛澎办,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播金砍,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼局蚀,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了恕稠?” 一聲冷哼從身側(cè)響起琅绅,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鹅巍,沒(méi)想到半個(gè)月后千扶,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體料祠,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年澎羞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了髓绽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡妆绞,死狀恐怖顺呕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情摆碉,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布脓豪,位于F島的核電站巷帝,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏扫夜。R本人自食惡果不足惜楞泼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望笤闯。 院中可真熱鬧堕阔,春花似錦、人聲如沸颗味。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)浦马。三九已至时呀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間晶默,已是汗流浹背谨娜。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留磺陡,地道東北人趴梢。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像币他,于是被迫代替她去往敵國(guó)和親坞靶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 《Effective C++ 中文版 第三版》讀書(shū)筆記 ** 條款 39:明智而審慎地使用 private 繼承 ...
    趙者也閱讀 361評(píng)論 0 0
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理蝴悉,服務(wù)發(fā)現(xiàn)滩愁,斷路器,智...
    卡卡羅2017閱讀 134,628評(píng)論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法辫封,類相關(guān)的語(yǔ)法硝枉,內(nèi)部類的語(yǔ)法廉丽,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法妻味,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,598評(píng)論 18 399
  • 再讀高效c++正压,頗有收獲,現(xiàn)將高效c++中的經(jīng)典分享如下责球,希望對(duì)你有所幫助焦履。 1、盡量以const \enum\i...
    橙小汁閱讀 1,208評(píng)論 0 1
  • 這本書(shū)屬于“想提高必看之書(shū)”雏逾,相見(jiàn)恨晚嘉裤,建議所有C++程序員都看看,沒(méi)事也可以拿出來(lái)翻翻栖博。大家也可以瀏覽下面的筆記...
    拉普拉斯妖kk閱讀 713評(píng)論 0 1