# [24] 繼承 — 私有繼承和保護繼承
## FAQs in section [24]:
* [24.1] 如何表示“私有繼承”?
* [24.2] 私有繼承和組合(composition)有什么類似?
* [24.3] 我應該選誰:組合還是私有繼承?
* [24.4] 從私有繼承類到父類需要指針類型轉換嗎?
* [24.5] 保護繼承和私有繼承的關系是什么?
* [24.6] 私有繼承和保護繼承的訪問規則是什么?
## 24.1 如何表示“私有繼承”?
用 `:?private` 代替 `:?public`,例如
```
?class?Foo?:?private?Bar?{
?public:
???//?...
?};
```
## 24.2 私有繼承和組合(composition)有什么類似?
私有繼承是組合的一種語法上的變形(聚合或者?“有一個”)
例如,“汽車有一個(has-a)引擎”關系可以用單一組合表示為:
```
?class?Engine?{
?public:
???Engine(int?numCylinders);
???void?start();?????????????????//?Starts?this?Engine
?};
?class?Car?{
?public:
???Car()?:?e_(8)?{?}?????????????//?Initializes?this?Car?with?8?cylinders
???void?start()?{?e_.start();?}??//?Start?this?Car?by?starting?its?Engine
?private:
???Engine?e_;????????????????????//?Car?has-a?Engine
?};
```
同樣的“有一個”關系也能用私有繼承表示:
```
?class?Car?:?private?Engine?{????//?Car?has-a?Engine
?public:
???Car()?:?Engine(8)?{?}?????????//?Initializes?this?Car?with?8?cylinders
???using?Engine::start;??????????//?Start?this?Car?by?starting?its?Engine
?};
```
兩種形式有很多類似的地方:
* 兩種情況中,都只有一個?Engine?被確切地包含于?Car?中
* 兩種情況中,在外部都不能將?`Car*`?轉換為?`Engine*`
* 兩種情況中,`Car`類都有一個`start()`方法,并且都在包含的`Engine`對象中調用`start()`方法。
也有一些區別:
* 如果你想讓每個?`Car`都包含若干?`Engine`,那么只能用單一組合的形式
* 私有繼承形式可能引入不必要的多重繼承
* 私有繼承形式允許?`Car`?的成員將?`Car*`?轉換成`Engine*`
* 私有繼承形式允許訪問基類的保護(`protected`)成員
* 私有繼承形式允許?`Car`?重寫?`Engine`?的虛函數
* 私有繼承形式賦予`Car`一個更簡潔(20個字符比28個字符)的僅通過 `Engine`調用的`start()`方法
注意,私有繼承通常用來獲得對基類的?protected:?成員的訪問,但這只是短期的解決方案(權宜之計)
## 24.3 我應該選誰:組合還是私有繼承?
盡可能用組合,萬不得已才用私有繼承
通常你不會想訪問其他類的內部,而私有繼承給你這樣的一些的特權(和責任)。但是私有繼承并不有害。只是由于它增加了別人更改某些東西時,破壞你的代碼的可能性,從而使維護的花費更昂貴。
當你要創建一個?`Fred`?類,它使用了?`Wilma`?類的代碼,并且`Wilma`?類的這些代碼需要調用你新建的?`Fred`?類的成員函數。在這種情況下,`Fred`??調用?`Wilma`?的非虛函數,而`Wilma`?調用(通常是純虛函數)被`Fred`重寫的這些函數。這種情況,用組合是很難完成的。
```
?class?Wilma?{
?protected:
???void?fredCallsWilma()
?????{
???????std::cout?<<?"Wilma::fredCallsWilma()\n";
???????wilmaCallsFred();
?????}
???virtual?void?wilmaCallsFred()?=?0;???//?純虛函數
?};
?class?Fred?:?private?Wilma?{
?public:
???void?barney()
?????{
???????std::cout?<<?"Fred::barney()\n";
???????Wilma::fredCallsWilma();
?????}
?protected:
???virtual?void?wilmaCallsFred()
?????{
???????std::cout?<<?"Fred::wilmaCallsFred()\n";
?????}
?};
```
## 24.4 從私有繼承類到父類需要指針類型轉換嗎?
一般來說,不。
對于該私有繼承類的成員函數或者友元來說,和基類的關系是已知的,并且這種從`PrivatelyDer*`?到?`Base*`?(或?`PrivatelyDer&`?到?`Base&`)的向上轉換是安全的,不需要也不推薦進行類型轉換。
然而,對于該私有繼承類(`PrivatelyDer`)的用戶來說,應該避免這種不安全的轉換。因為它基于`PrivatelyDer`的私有實現,它可以自行改變。
## 24.5 保護繼承和私有繼承的關系是什么?
相同點:都允許重寫私有/保護基類的虛函數,都不表明派生類“是一種(a kind-of)”基類。
不同點:保護繼承允許派生類的派生類知道繼承關系。如此,子孫類可以有效的得知祖先類的實現細節。這樣既有好處(它允許保護繼承的子類使用它和保護基類的關聯)也有代價(保護派生類不能在無潛在破壞更深派生類的情況下改變這種關聯)。
保護繼承使用?`:?protected`?語法:
```
?class?Car?:?protected?Engine?{
?public:
???//?...
?};
```
## 24.6 私有繼承和保護繼承的訪問規則是什么?
以這些類為例:
```
?class?B????????????????????{?/*...*/?};
?class?D_priv?:?private???B?{?/*...*/?};
?class?D_prot?:?protected?B?{?/*...*/?};
?class?D_publ?:?public????B?{?/*...*/?};
?class?UserClass????????????{?B?b;?/*...*/?};
```
子類都不能訪問?`B`?的私有部分。在?`D_priv`中,B?的公有和保護部分都是私有的。在?`D_prot`中,`B`?的公有和保護部分都是保護的。在?`D_publ`?中,`B`?的公有部分是公有的,`B`?的保護部分是保護的(`D_publ`?是一種?`B`)。`UserClass`?類僅僅可以訪問?`B`?的公有部分。
要使?`B`?的公有成員在?`D_priv`?或?`D_prot`中也是公有的,則使用?`B::`?前綴聲明成員的名稱。例如,要使?`B::f(int,float)`成員在?`D_prot`中公有,應該這樣寫:
```
?class?D_prot?:?protected?B?{
?public:
???using?B::f;??//?注意:?不是?using?B::f(int,float)
?};
```
- C++ FAQ Lite
- [1] 復制許可
- [2] 在線站點分發本文檔
- [3] C++-FAQ-Book 與 C++-FAQ-Lite
- [6] 綜述
- [7] 類和對象
- [8] 引用
- [9] 內聯函數
- [10] 構造函數
- [11] 析構函數
- [12] 賦值算符
- [13] 運算符重載
- [14] 友元
- [15] 通過 &lt;iostream&gt; 和 &lt;cstdio&gt;輸入/輸出
- [16] 自由存儲(Freestore)管理
- [17] 異常和錯誤處理
- [18] const正確性
- [19] 繼承 — 基礎
- [20] 繼承 — 虛函數
- [21] 繼承 — 適當的繼承和可置換性
- [22] 繼承 — 抽象基類(ABCs)
- [23] 繼承 — 你所不知道的
- [24] 繼承 — 私有繼承和保護繼承
- [27] 編碼規范
- [28] 學習OO/C++
- [31] 引用與值的語義
- [32] 如何混合C和C++編程
- [33] 成員函數指針
- [35] 模板 ?
- [36] 序列化與反序列化
- [37] 類庫