揭開私有繼承的面紗

2021-06-07 22:48:25 字數 4184 閱讀 8594

什麼是私有繼承?以前在學校學習的時候,冥冥乎知道有這樣乙個東西,卻沒有仔細研究過。後來工作中用到boost庫才開始了解它。如果說保護繼承大多是為了語言完整性的話,私有繼承還是有一些用途的。

私有繼承 vs 公有繼承

公有繼承繼承的是

介面與實現

,它表示了類與類之間

is-a

的關係。而私有繼承繼承的僅僅是

實現,它表示了

has-a

(或者is-implemented-in-terms-of

)的關係。

在公有繼承中,基類的公有成員在派生類中仍然公有,基類擁有的介面完整無缺地傳給了派生類,也就是說基類物件出現的每乙個地方都可以用派生類物件替換(is-a)。因此,編譯器可以安全地把派生類物件的引用、指標隱式轉換為基類物件的引用、指標。而在私有繼承中,基類的公有和保護成員在派生類中變成了私有,從外界來看,派生類物件不再擁有基類的行為。因此,編譯器不會做類似的轉換。同樣,物件切片(object slicing)也只有在公有繼承中才會出現。

class

base  

};  

class

derived1: 

public

base  

{};  

class

derived2: 

private

base  

{};  

void

method(base &b)  

intmain()    

私有繼承的本質

私有繼承隱藏了基類的介面,但這些被隱藏的函式在派生類的成員函式中是可以呼叫的。所以,你在實現派生類函式的時候可以呼叫基類物件函式來完成部分功能(is-implemented-in-terms-of)。下面這個例子中,企鵝類私有繼承了鳥類。沒有公有繼承是因為企鵝並不是嚴格意義上的鳥,因為它們不會飛。如果我們要求所有的企鵝在走完路以後都要蹦一下,我們完全可以重用基類的bird::walk來完成走路這個子過程。

class

bird  

;  class

penguin: 

private

bird  

private

:  void

jump();  

};  

私有繼承 vs 組合

實際上,私有繼承只是實現 has-a 的方式之一,物件組合(composition/aggregation/containment)同樣可以達到相同的目的。還是考慮企鵝的例子,鳥類物件作為了企鵝類的乙個私有成員,企鵝的行走動作是通過呼叫該私有成員(b)的公有成員函式(walk)來完成的。企鵝和鳥物件之間是一種包含關係。

class

bird  

;  class

penguin  

private

:  void

jump();  

bird b;  

};  

同樣是 has-a 的關係,它們兩個各有什麼優缺點呢?

組合的優點

(一)組合相對於繼承來說耦合度較小,特別是當penguin類hold了乙個bird物件的指標而非物件時。這時候包含penguin定義的標頭檔案甚至都不用包含bird.h而只用乙個向前宣告即可。這樣的好處是,當bird的內部實現發生變化時,penguin類不需要重新編譯。這在大型專案中非常重要。

// bird.h

class

bird  

;  // penguin.h

class

bird;  

class

penguin  

;  

(二)penguin類可以同時有多個bird類的物件成員b1,b2.. 繼承是無法做到這點的。

私有繼承的優點

(一)你要繼承乙個類,但只希望保留其中的部分公用介面。首先,公有繼承肯定不合適,因為基類介面必須全部保留。用組合可以實現,但是由於組合不會把子物件的介面暴露出來,你需要重新定義那些你希望保留的介面。為了實現介面,你需要呼叫子物件的相應函式。相比較之下,私有繼承就簡單很多。因為基類的介面派生類都有,只不過是私有的,重新把它們宣告為公有即可。比如下面這個例子,派生類只希望暴露f1和f2這兩個介面。

class

base  

;  // 使用私有繼承,暴露部分介面的工作變得非常簡單

class

derived1: 

private

base  

;  // 使用組合,稍微複雜些

class

derived2  

void

f2()   

private

:  base b;  

};  

(二)如果你希望重新定義乙個類的虛函式,那麼只好用私有繼承。這點並沒有看上去那麼容易理解:由於是私有繼承,怎樣才能實現執行時多型呢?畢竟,基類的指標指向派生類物件是不允許的。如果不能調到派生類重新定義的虛函式,我們實現它又有什麼意義呢?下面這個例子給出了一種可能的模型。

class

base  

// 相當於this->vf(),執行時多型

protected

:  virtual

void

vf()   

};  

class

derived: 

private

base  

private

:  virtual

void

vf()   

};  

intmain()    

(三)如果要保證乙個類不具有拷貝屬性(例如mutex,資料庫連線等),應該怎麼做?一種方法是把拷貝建構函式、賦值運算子私有化。但還是有乙個問題,類的成員函式還是可以呼叫它們。乙個更好的辦法是繼承boost::noncopyable。只有當有物件拷貝動作的時候,編譯器才會合成出拷貝建構函式等,這時候編譯出錯,因為基類的拷貝函式無法訪問;當沒有拷貝動作時,不會生成拷貝函式,一切正常。注意,由於noncopyable的構造析構是保護屬性,組合方式不可行,只能使用繼承。雖然私有和公有繼承技術上都可行(因為所有noncopyable的函式都已經不是公有了),但私有繼承更好,因為這裡正是乙個is-implemented-in-terms-of 而非is-a 的關係。

class

noncopyable  

~noncopyable() {}  

private

:  // emphasize the following members are private

noncopyable( const

noncopyable& );  

const

noncopyable& operator=( 

const

noncopyable& );  

};  

class

myclass: 

private

boost::noncopyable  

intmain    

(四)最後,當你需要用到乙個類保護成員的時候,不得不用私有繼承。因為保護成員對於外界不可見,但對於派生類可見。

虛析構函式的思考

當私有繼承乙個基類時,派生類物件不再是基類物件,編譯器也不會執行隱式型別轉換,因此也不會出現通過基類指標釋放派生類物件的問題。所以,如果你寫了乙個類不打算做基類,或者只打算作為以後派生類的實現(私有繼承),那麼大可不必有虛析構函式。上面的 boost::noncopyable 就是乙個例子。

小結:私有繼承中,基類所有的成員在派生類中都為私有。編譯器也因此不會做從派生類到基類的隱式型別轉換。

公有繼承表示is-a 的關係,私有繼承表示has-a 或is-implemented-in-terms-of 的關係。

私有繼承和組合都表示 has-a 關係,各有千秋。一般來說,能用組合盡量用組合,因為它鬆耦合,維護也比較簡單直觀。當必須使用私有繼承時才使用它。

揭開私有繼承的面紗

什麼是私有繼承?以前在學校學習的時候,冥冥乎知道有這樣乙個東西,卻沒有仔細研究過。後來工作中用到boost庫才開始了解它。如果說保護繼承大多是為了語言完整性的話,私有繼承還是有一些用途的。私有繼承 vs 公有繼承 公有繼承繼承的是 介面與實現 它表示了類與類之間 is a 的關係。而私有繼承繼承的僅...

揭開私有繼承的面紗

原文 什麼是私有繼承?以前在學校學習的時候,冥冥乎知道有這樣乙個東西,卻沒有仔細研究過。後來工作中用到boost庫才開始了解它。如果說保護繼承大多是為了語言完整性的話,私有繼承還是有一些用途的。私有繼承 vs 公有繼承 公有繼承繼承的是 介面與實現 它表示了類與類之間 is a的關係。而私有繼承繼承...

Caffe 初識,揭開面紗

這一段時間把caffe官網上的例子跑了一下,對caffe有了乙個大概的了解。如果你想對caffe有個比較清晰的了解,建議認真閱讀官網上的資料,尤其在caffe資料極少的情況下,這種方法是最有效的途徑,可以讓你少走許多彎路,不要上來就在網上隨便找個教程配置環境,上來就想跑例子。博主就是赤裸裸的例子,在...