條款 39:明智而審慎地使用 private 繼承

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

** 條款 39:明智而審慎地使用 private 繼承 **

C++ 中 public 繼承視為 is-a 關(guān)系。現(xiàn)在看 private 繼承:

class Person{...}; 

class Student: private Person {...}; 

void eat(const Person& p); 

void study(const Student& s);

Person p; 

Student s; 

eat(p); 

eat(s); // 錯(cuò)誤第晰! 難道學(xué)生不是人棵里?过蹂!

顯然 private 繼承不是 is-a 關(guān)系族檬。

由 private base class 繼承而來(lái)的所有成員茬腿,在 derived class 中都會(huì)成為 private 屬性呼奢,縱使它們?cè)?base class 中原本是 protected 或 public。private 繼承意味著 implemented-in-term-of(根據(jù)某物實(shí)現(xiàn))切平。

如果 class D 以 private 形式繼承 class B握础,用意是為了采用 class B 內(nèi)已經(jīng)備妥的某些特性,不是因?yàn)?B 對(duì)象和 D 對(duì)象存在任何觀念上的關(guān)系悴品。private 繼承純粹只是一種實(shí)現(xiàn)技術(shù)(這就是為什么繼承自 private base class 的每樣?xùn)|西在你的 class 內(nèi)都是 private:因?yàn)樗麄兌际菍?shí)現(xiàn)細(xì)節(jié)而已)禀综。用條款 34 的術(shù)語(yǔ)說(shuō),private 繼承意味著只有實(shí)現(xiàn)部分被繼承苔严,接口部分應(yīng)略去定枷。如果 D 以 private 形式繼承 B,意思是 D 對(duì)象根據(jù) B 對(duì)象實(shí)現(xiàn)而得届氢。private 繼承在軟件 “設(shè)計(jì)” 層面上沒(méi)有意義欠窒,其意義只及于軟件實(shí)現(xiàn)層面。

條款 38 說(shuō)復(fù)合(composition)的意義也是 is-implemented-in-term-of退子,如何進(jìn)行取舍岖妄?盡可能的使用復(fù)合型将,必要時(shí)才使用 private 繼承:主要是當(dāng)一個(gè)意欲成為 derived class 者想訪(fǎng)問(wèn)一個(gè)意欲成為 base class 者的 protected 成分,或?yàn)榱酥匦露x virtual 函數(shù)荐虐,還有一種激進(jìn)情況是空間方面的厲害關(guān)系七兜。

有個(gè) Widget class,它記錄每個(gè)成員函數(shù)的被調(diào)用次數(shù)福扬。運(yùn)行期周期性的審查那份信息腕铸。為了完成這項(xiàng)工作,我們需要設(shè)定某種定時(shí)器忧换,使我們知道收集統(tǒng)計(jì)數(shù)據(jù)的時(shí)候是否到了恬惯。

為了復(fù)用既有代碼,我們發(fā)現(xiàn)了 Timer:

class Timer { 
public: 
    explicit Timer(int tickFrequency); 
    virtual void onTick() const; 
};

每次滴答就調(diào)用某個(gè) virtual 函數(shù)亚茬,我們可以重新定義那個(gè) virtual 函數(shù),讓后者取出 Widget 的當(dāng)時(shí)狀態(tài)浓恳。

為了讓 Widget 重新定義 Timer 內(nèi)的 virtual 函數(shù)刹缝,Widget 必須繼承自 Timer。但 public 繼承并不適當(dāng)颈将,因?yàn)?Widget 并不是一個(gè) Timer梢夯。不能夠?qū)σ粋€(gè) Widget 調(diào)用 onTick 吧,觀念上那并不是 Wigdet 接口的一部分晴圾。

我們必須用 private 繼承 Timer:

class Widget: private Timer{ 
private: 
    virtual void onTick() const; 
};

再說(shuō)一次颂砸,把 onTick 放進(jìn) public 接口內(nèi)會(huì)導(dǎo)致客戶(hù)以為他們可以調(diào)用它,那就違反了條款 18.

這個(gè)設(shè)計(jì)好死姚,但不值幾文錢(qián)人乓,private 繼承并非絕對(duì)必要。如果我們決定用復(fù)合取而代之都毒,是可以的色罚,只要在 Widget 內(nèi)聲明一個(gè)嵌套式 private class,后者以 public 形式繼承 Timer 并重新定義 onTick账劲,然后放一個(gè)這種類(lèi)型的對(duì)象在 Widget 內(nèi):

class Widget{ 
private: 
    class WidgetTimer: public Timer{ 
    public: 
        virtual void onTick() const; 
    }; 

    WidgetTimer timer; 
};

這個(gè)設(shè)計(jì)比只是用 private 繼承復(fù)雜一些戳护,但是有兩個(gè)理由可能你愿意或應(yīng)該選擇這樣的 public 繼承加復(fù)合:

首先,或許會(huì)想設(shè)計(jì) Widget 使它得以擁有 derived classes瀑焦,但同時(shí)你可能會(huì)想阻止 derived clssses 重新定義 onTick腌且。如果 Widget 繼承自 Timer,上面的想法就不可能實(shí)現(xiàn)榛瓮,即使是 private 繼承也不可能铺董。(條款 35 說(shuō) derived class 可以重新定義 virtual 函數(shù),即使它們不得調(diào)用它)但如果 WidgetTimer 是 Widget 內(nèi)部的一個(gè) private 成員并繼承 Timer榆芦,Widget 的 derived classes 將無(wú)法取用 WidgetTimer柄粹,因此無(wú)法繼承它或重新定義它的 virtual 函數(shù)喘鸟。有些類(lèi)似 java 的 final 或 C# 的 sealed。

第二驻右,或許想要將 Widget 的編譯依存性降至最低什黑,若 Widget 繼承 Timer,當(dāng) Widget 被編譯時(shí)堪夭,timer 的定義必須可見(jiàn)愕把,所以定義 Widget 的那個(gè)文件必須包含 Timer.h。但如果 WidgetTimer 移除 Widget 之外而 Widget 內(nèi)含一個(gè)指針指向 WidgetTimer森爽,Widget 可以只帶著一個(gè)簡(jiǎn)單的 WidgetTimer 聲明式恨豁,不再需要 #include 任何與 timer 有關(guān)的東西。對(duì)大型系統(tǒng)而言爬迟,如此的解耦(decouplings)可能是重要的措施橘蜜。

還有一種激進(jìn)情況,只適用于你所處理的 class 不帶任何數(shù)據(jù)時(shí)付呕。這樣的 classes 沒(méi)有 non-static 成員變量计福,沒(méi)有 virtual 函數(shù)(這種函數(shù)會(huì)為每個(gè)對(duì)象帶來(lái)一個(gè) vptr),也沒(méi)有 virtual base classes(這樣的 base classes 也會(huì)招致體積上的額外開(kāi)銷(xiāo)徽职,見(jiàn)條款 40)象颖。這種所謂的 empty class 對(duì)象不使用任何空間,因?yàn)闆](méi)有任何隸屬對(duì)象的數(shù)據(jù)要存儲(chǔ)姆钉,然而由于技術(shù)上的理由说订,C++ 裁定凡是獨(dú)立(非附屬)對(duì)象都必須有非零大小:

class Empty{}; 

class HoldsAnInt { 

private: 
    int x; 
    Empty e; // 應(yīng)該不需要任何內(nèi)存 
};

你會(huì)發(fā)現(xiàn) sizeof(HoldsAnInt) > sizeof(int)潮瓶;一個(gè) Empty 成員變量竟然要求內(nèi)存陶冷。在多數(shù)編譯器中 sizeof(Empty) 獲得 1,因?yàn)槊鎸?duì) “大小為零的獨(dú)立對(duì)象”筋讨,通常 C++ 官方勒令默默安插一個(gè) char 到空對(duì)象內(nèi)埃叭。然而齊位需求(alignment)可能造成編譯器為類(lèi)似 HoldsAnInt 這樣的 class 加上一些襯墊(padding),所以有可能 HoldsAnInt 對(duì)象不只多一個(gè) char 大小悉罕,實(shí)際上放大到多一個(gè) int赤屋。

獨(dú)立(非附屬)這個(gè)約束不適用于 derived class 對(duì)象內(nèi)的 base class 成分,因?yàn)樗鼈儾⒎仟?dú)立壁袄。如果你繼承 Empty类早,而不是內(nèi)含一個(gè)那種類(lèi)型的對(duì)象:

class HoldsAnInt: private Empty{ 
private: 
    int x; 
};

幾乎可以確定 sizeof(HoldsAnInt) == sizeof(int)。這是所謂的 EBO(empty base optimization:空白基類(lèi)最優(yōu)化)嗜逻,如果你是一個(gè)庫(kù)開(kāi)發(fā)成員涩僻,而你的客戶(hù)非常在意空間,那么值得注意 EBO。另外一個(gè)值得知道的是逆日,一般 EBO 只在單一繼承(而非多繼承)下才可行嵌巷。

現(xiàn)實(shí)中的 “Empty” class 并不是真的 empty。雖然他們從未擁有 non-static 成員變量室抽,卻往往內(nèi)含 typedefs搪哪, enums, static 成員變量坪圾,或 non-virtual 函數(shù)晓折。stl 就有許多技術(shù)用途的 empty classes,其中內(nèi)含有用的成員(通常是 typedefs)兽泄,包括 base classes unary_function 和 binary_function漓概,這些是 “用戶(hù)自定義的函數(shù)對(duì)象” 通常都會(huì)繼承的 classes。感謝 EBO 的廣泛實(shí)踐病梢,這樣的繼承很少增加 derived classes 的大小胃珍。

盡管如此,大多數(shù) class 并非 empty蜓陌,所以 EBO 很少成為 private 繼承的正當(dāng)理由堂鲜。復(fù)合和 private 繼承都意味著 is-implemented-in-term-of,但復(fù)合比較容易理解护奈,所以無(wú)論什么時(shí)候,只要可以哥纫,還是應(yīng)該選擇復(fù)合霉旗。

請(qǐng)記住:

  1. private 繼承意味 is-implementation-in-terms of(根據(jù)某物實(shí)現(xiàn)出)蛀骇。她通常比復(fù)合級(jí)別低厌秒。但是當(dāng) derived class 需要訪(fǎng)問(wèn) protected base class 的成員,或需要重新定義繼承而來(lái)的 virtual 函數(shù)時(shí)擅憔,這么設(shè)計(jì)是合理的鸵闪。

  2. 和復(fù)合不同,private 繼承可以造成 empty base 最優(yōu)化暑诸。這對(duì)致力于 “對(duì)象尺寸最小化” 的程序庫(kù)開(kāi)發(fā)者而言蚌讼,可能很重要。

最后編輯于
?著作權(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)店門(mé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)容

  • 1. public繼承 以C++進(jìn)行面向?qū)ο缶幊潭角荩钪匾囊粋€(gè)規(guī)則是:public inheritance(公開(kāi)繼...
    何幻閱讀 1,511評(píng)論 0 1
  • 《Effective C++ 中文版 第三版》讀書(shū)筆記 ** 條款 40:明智而審慎地使用多重繼承 ** 一旦涉及...
    趙者也閱讀 433評(píng)論 0 0
  • 再讀高效c++脆霎,頗有收獲,現(xiàn)將高效c++中的經(jīng)典分享如下狈惫,希望對(duì)你有所幫助睛蛛。 1、盡量以const \enum\i...
    橙小汁閱讀 1,208評(píng)論 0 1
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)忆肾,斷路器荸频,智...
    卡卡羅2017閱讀 134,628評(píng)論 18 139
  • Introduction to C++ (Season 1) Unit 1: Overview of C++ 第1...
    我是阿喵醬閱讀 2,734評(píng)論 0 7