條款32曾經論證過C++如何将public繼承視為is-a關系。在那個例子中我們有個繼承體系,其中class Student以public形式繼承class Person,于是編譯器在必要時刻(為了讓函數調用成功)将Students暗自轉換為Persons。現在我們再重複該例的一部分,并以private繼承替換public繼承:
class Person { ... };
class Student:private Person { ... }; // 這次改用private繼承
void eat(const Person& p); // 任何人都會吃
void study(const Student& s); // 隻有學生才到校學習
Person p; // p是人
Student s; // s是學生
eat(p); // 沒問題,p是人
eat(s); // 錯誤!吓,難道學生不是人?!
顯然private繼承并不意味is-a關系。那麼它意味什麼?
如果classes之間的繼承關系是private,編譯器不會自動将一個derived class對象(例如Student)轉換為一個base class對象(例如Person)。這和public繼承的情況不同。這也就是為什麼通過s調用eat會失敗的原因。第二條規則是,由private base class繼承而來的所有成員,在derived class中都會變成private屬性,縱使它們在base class中原本是protected或public屬性。
Private繼承意味implemented-in-terms-of(根據某物實作出)。如果你讓class D以private形式繼承class B,你的用意是為了采用class B内已經備妥的某些特性,不是因為B對象和D對象存在有任何觀念上的關系。private繼承純粹隻是一種實作技術(這就是為什麼繼承自一個private base class的每樣東西在你的class内都是private:因為它們都隻是實作枝節而已)。借用條款34提出的術語,private繼承意味隻有實作部分被繼承,接口部分應略去。如果D以private形式繼承B,意思是D對象根據B對象實作而得,再沒有其他意涵了。Private繼承在軟體“設計”層面上沒有意義,其意義隻及于軟體實作層面。
Private繼承意味is-implemented-in-terms-of(根據某物實作出),這個事實有點令人不安,因為條款38才剛指出複合(composition)的意義也是這樣。你如何在兩者之間取舍?答案很簡單:盡可能使用複合,必要時才使用private繼承。何時才是必要?主要是當protected成員和/或 virtual函數牽扯進來的時候。
考慮一個執行個體,修改Widgets class,讓它記錄每個成員函數的被調用次數。先找到如下class:
class Timer {
public:
explicit Timer(int tickFrequency);
virtual void onTick() const; // 定時器每滴答一次,此函數就自動調用一次
...
};
為了讓Widget重新定義Timer内的virtual函數,Widget必須繼承自Timer。但public繼承在此例并不适當,因為Widget并不是個Timer。是呀,Widget客戶總不該能夠對着一個Widget調用onTick吧,因為觀念上那并不是Widget接口的一部分。如果允許那樣的調用動作,很容易造成客戶不正确地使用Widget接口,那會違反條款18的忠告:“讓接口容易被正确的使用,不易被誤用”。在這裡,public繼承不是個好政策。
我們必須以private形式繼承Timer:
class Widget: private Timer {
private:
virtual void onTick() const; // 檢視Widget的資料...等等。
...
};
這是個好設計,但不值幾文錢,因為private繼承并非絕對必要。如果我們決定以複合(composition)取而代之,是可以的。隻要在Widget内聲明一個嵌套式private class,後者以public形式繼承Timer并重新定義onTick,然後放一個這種類型的對象于Widget内。下面是這種解法的草樣:
class Widget {
private:
class WidgetTimer: public Timer {
virtual void onTick() const;
...
};
WidgetTimer timer;
...
};

這個設計比隻使用private繼承要複雜一些些,因為它同時涉及public繼承和複合,并導入一個新class(WidgetTimer)。坦白說,我展示它主要是為了提醒你,解決一個設計問題的方法不隻一種,而訓練自己思考多種做法是值得的(看看條款35)。盡管如此,我可以想出兩個理由,為什麼你可能願意(或說應該)選擇這樣的public繼承加複合,而不是選擇原先的private繼承設計。
首先,你或許會想設計Widget使它得以擁有derived classes,但同時你可能會想阻止derived classes重新定義onTick。如果Widget繼承自Timer,上面的想法就不可能實作,即使是private繼承也不可能。(還記得嗎,條款35曾說derived classes可以重新定義virtual函數,即使它們不得調用它。)
第二,你或許會想要将Widget的編譯依存性降至最低。如果Widget繼承Timer,當Widget被編譯時Timer的定義必須可見,是以定義Widget的那個檔案恐怕必須#include Timer.h。但如果WidgetTimer移出Widget之外而Widget内含指針指向一個WidgetTimer,Widget可以隻帶着一個簡單的WidgetTimer聲明式,不再需要#include任何與Timer有關的東西。對大型系統而言,如此的解耦可能是重要的措施。關于編譯依存性最小化,詳見條款31。
大多數繼承相當于is-a,這是指public繼承,不是private繼承。複合和private繼承都意味is-implemented-in-terms-of,但複合比較容易了解,是以無論什麼時候,隻要可以,你還是應該選擇複合。
請記住
- Private繼承意味is-implemented-in-terms-of(根據某物實作出)。它通常比複合(composition)的級别低。但是當derived class需要通路protected base class的成員,或需要重新定義繼承而來的virtual函數時,這麼設計是合理的。
- 和複合(composition)不同,private繼承可以造成empty base最優化。這對緻力于“對象尺寸最小化”的程式庫開發者而言,可能很重要。