Private繼承和public繼承是軟件生產中兩個不同層面上的東西。Public側重的是業務邏輯,它是從用戶角度來想問題的。而private則純粹是從實現角度來講的,它意味著is-implemented-in-terms-of,即根據某物實現出什么的意思。
首先看一個例子:
可以看出通過子類對象無法調用父類的公有接口,而child和base是通過private方式繼承的。這說明通過private方式繼承的子類對象并不是一個父類對象,即不是IS-A的關系。
在面向對象程序設計的過程通常是模塊化編程,每個模塊是按照功能進行劃分的,模塊之外的通常都是用戶,用戶可調用的就是這個模塊提供的接口,它們通常是public成員函數。而private繼承體現了模塊內部純粹的實現細節和代碼重用,因此private繼承主要用在應用已經存在的具體實現實現出新的實現。
那這里private和復合都是is-implemented-in-terms-of關系,用哪個好呢?在這里作者極力推薦使用復合,不到萬不得已不使用private繼承,這倒不是說private繼承不好,只是想表達private繼承沒啥必要,但是為啥沒必要作者并未提及,作者只是說實現途徑不只有private而已,這不禁讓我感覺作者是在炫技或者純粹是別出心裁思維的一種表述。
那么這個萬不得已的時候是什么時候?作者說當涉及到protected成員、virtual成員函數、空間被限制得特別小特別嚴格的時候。
作者舉了一個例子用來說明一種方案來代替private繼承,我在這里就不再贅述這個例子是啥了,我只說說原理就OK了。因為當父類中含有virtual函數的時候,由于virtual函數的特性使之可在子類中被重新定義,因此不適宜使用public繼承,不使用public繼承,那自然就不滿足IS-A的關系啦。
在這里使用private繼承是一種好的選擇,因為它有效地隱藏實現的細節,并且提供一個不具二義性的接口給用戶。
它的一個替代實現方案是最靠近的是一個功能類A,它需要用到類B的實現,而這個類B又具備類C的所有特性。那現在的設計就是類A中包含了private的類B,并且類B以public方式繼承了類C。然后在類A中以private方式聲明一個類B的對象。這樣所有涉及到virtual函數的重定義方面的事情都集中到B中進行處理和A一點關系都沒有。這也是實現數據封裝的一種方式,以后即便類A擁有自己的子類D,D也不會涉及到處理virtual函數的事。這樣做也可以降低編譯相依性,因為如果A直接繼承C勢必會導致加載C所在頭文件,而如果按照上述方案A只需要B的一個聲明。作者提到的這個方案還是個重要的解耦方案呢。
為什么說private繼承適用于那些父類中有protected和virtual函數的場合呢?那是因為在private繼承下繼承的protected成員到子類中就是子類的private成員,父類的private成員還是父類的private成員跟子類一點關系都沒有,而父類的public成員是面向用戶的,它不需要子類去繼承。所以你可以看出但凡是需要private繼承的父類中最多只含有protected和private成員。而父類中如果含有virtual函數,那么它存在的目的就是要你去重定義,或者使用默認的實現,它不是public接口,還是實現的層面,而子類實現出來的東西就不是父類中的原版了,也就是說這不是IS-A的關系了,自然就不是public繼承。那就只可能是private和protected繼承了。而在這里我大膽地猜想protected繼承應該是適用于那些連續繼承實現的場合吧。因為如果是private繼承,父類中的非private成員都會變成子類的private繼承,而子類的private成員無論通過什么繼承方式都無法再被其他類繼承了。所以我覺得private繼承是protected繼承的終結。
那么對空間限制非常嚴格非常小的場合又是什么呢?這涉及到空類的繼承與復合的區別。現在假設類A是空類,里面啥都沒有,類B是正常類,那么有B復合A所占用的空間比B繼承A所占用的空間大。具體來講B中的各成員的存儲的時候要求對齊存儲,這一點我在《C與指針》有關結構體各成員在內存中的存儲情況有過記錄,類中各成員的存儲情況也是類似的。而且一般來講一個空類的對象所占的空間也不是0,一般情況下是1byte,因為C++規定一個獨立的對象所占用的空間必須非0。這就決定你在采用復合時那個復合類對象所占用的空間中空類成員占用不只1byte。而你如果繼承空類,那么子類對象中空類成分占用0byte。這種技巧被稱為EBO(empty base optimization,空白基類最優化),不過它一般用于單繼承。
它的作用在一般情況下是體現不出來的,因為一般的存儲空間已經足夠大了。它的實用性真正體現在像什么嵌入式這種空間非常小的場合。還有這個EBO技術所說的空類,其實也并不是啥都沒有,typedef、enum、static、非virtual函數還是可能有的。請看下圖:
其實,只要類中不含有非static成員變量,它就被認為是空的,比如說我再往上述例子中添加一個非static成員變量再看。
最后作者總結道:
1、private繼承強調的是實現,它比復合低。當子類需要訪問父類中的protected成員或者父類中還有virtual函數時,你需要private繼承。
2、private是和空間最優化設計。