# Item 34: 區分 inheritance of interface(接口繼承)和 inheritance of implementation(實現繼承)
作者:Scott Meyers
譯者:fatalerror99 (iTePub's Nirvana)
發布:http://blog.csdn.net/fatalerror99/
(public) inheritance 這個表面上簡單易懂的觀念,一旦被近距離審視,就會被證明是由兩個相互獨立的部分組成的:inheritance of function interfaces(函數接口的繼承)和 inheritance of function implementations(函數實現的繼承)。這兩種 inheritance 之間的差異正好符合本書 Introduction 中論述的 function declarations(函數聲明)和 function definitions(函數定義)之間的差異。
作為一個 class 的設計者,有的時候你想要 derived classes 只繼承一個 member function 的 interface (declaration)。有的時候你想要 derived classes 既繼承 interface(接口)也繼承 implementation(實現),但你要允許它們替換他們繼承到的 implementation。還有的時候你想要 derived classes 繼承一個函數的 interface(接口)和 implementation(實現),而不允許它們替換任何東西。
為了更好地感覺這些選擇之間的不同之處,考慮一個在圖形應用程序中表示幾何圖形的 class hierarchy(類繼承體系):
```
class Shape {
public:
virtual void draw() const = 0;
virtual void error(const std::string& msg);
int objectID() const;
...
};
class Rectangle: public Shape { ... };
class Ellipse: public Shape { ... };
```
Shape 是一個 abstract class(抽象類),它的 pure virtual function(純虛擬函數)表明了這一點。作為結果,客戶不能創建 Shape class 的實例,只能創建從它繼承的 classes 的實例。但是,Shape 對所有從它(公有)繼承的類施加了非常強大的影響,因為
成員函數 interfaces are always inherited。就像 Item 32 解釋的,public inheritance 意味著 is-a,所以對一個 base class 來說成立的任何東西,對于它的 derived classes 也必須成立。因此,如果一個函數適用于一個 class,它也一定適用于它的 derived classes。
Shape class 中聲明了三個函數。第一個,draw,在一個明確的顯示設備上畫出當前對象。第二個,error,如果 member functions 需要報告一個錯誤,就調用它。第三個,objectID,返回當前對象的唯一整型標識符。每一個函數都用不同的方式聲明:draw 是一個 pure virtual function(純虛擬函數);error 是一個 simple (impure?) virtual function(簡單虛擬函數);而 objectID 是一個 non-virtual function(非虛擬函數)。這些不同的聲明暗示了什么呢?
考慮第一個 pure virtual function(純虛擬函數)draw:
```
class Shape {
public:
virtual void draw() const = 0;
...
};
```
pure virtual functions(純虛擬函數)的兩個最顯著的特性是它們必須被任何繼承它們的具體類重新聲明,和抽象類中一般沒有它們的定義。把這兩個特性加在一起,你應該認識到
聲明一個 pure virtual function(純虛擬函數)的目的是使 derived classes 繼承一個函數 interface only。
這就使 Shape::draw function 具有了完整的意義,因為它要求所有的 Shape 對象必須能夠畫出來是合情合理的,但是 Shape class 本身不能為這個函數提供一個合乎情理的缺省的實現。例如,畫一個橢圓的算法和畫一個矩形的算法是非常不同的,Shape::draw 的聲明告訴具體 derived classes 的設計者:“你必須提供一個 draw function,但是我對于你如何實現它不發表意見。”
順便提一句,為一個 pure virtual function(純虛擬函數)提供一個定義是有可能的。也就是說,你可以為 Shape::draw 提供一個實現,而 C++ 也不會抱怨什么,但是調用它的唯一方法是用 class name 限定修飾這個調用:
```
Shape *ps = new Shape; // error! Shape is abstract
Shape *ps1 = new Rectangle; // fine
ps1->draw(); // calls Rectangle::draw
Shape *ps2 = new Ellipse; // fine
ps2->draw(); // calls Ellipse::draw
ps1->Shape::draw(); // calls Shape::draw
ps2->Shape::draw(); // calls Shape::draw
```
除了幫助你在雞尾酒會上給同行程序員留下印象外,這個特性通常沒什么用處,然而,就像下面你將看到的,它能用來作為一個“為 simple (impure) virtual functions 提供一個 safer-than-usual 的實現”的機制。
simple virtual functions 背后的故事和 pure virtuals 有一點不同。derived classes 照常還是繼承函數的 interface,但是 simple virtual functions 提供了一個可以被 derived classes 替換的實現。如果你為此考慮一陣兒,你就會認識到
聲明一個 simple virtual function 的目的是讓 derived classes 繼承一個函數 interface as well as a default implementation。
考慮 Shape::error 的情況:
```
class Shape {
public:
virtual void error(const std::string& msg);
...
};
```
interface 要求每一個 class 必須支持一個在遭遇到錯誤時被調用的函數,但是每一個 class 可以自由地用它覺得合適的任何方法處理錯誤。如果一個 class 不需要做什么特別的事情,它可以僅僅求助于 Shape class 中提供的錯誤處理的缺省版本。也就是說,Shape::error 的聲明告訴 derived classes 的設計者:“你應該支持一個 error function,但如果你不想自己寫,你可以求助 Shape class 中的缺省版本。”
結果是:允許 simple virtual functions 既指定一個函數接口又指定一個缺省實現是危險的。來看一下為什么,考慮一個 XYZ 航空公司的飛機的 hierarchy(繼承體系)。XYZ 只有兩種飛機,Model A 和 Model B,它們都嚴格地按照同樣的方法飛行。于是,XYZ 設計如下 hierarchy(繼承體系):
```
class Airport { ... }; // represents airports
class Airplane {
public:
virtual void fly(const Airport& destination);
...
};
void Airplane::fly(const Airport& destination)
{
default code for flying an airplane to the given destination
}
class ModelA: public Airplane { ... };
class ModelB: public Airplane { ... };
```
為了表述所有的飛機必須支持一個 fly 函數,并為了“不同機型可能(在理論上)需要不同的對 fly 的實現”的事實,Airplane::fly 被聲明為 virtual。然而,為了避免在 ModelA 和 ModelB classes 中些重復的代碼,缺省的飛行行為由 Airplane::fly 的函數體提供,供 ModelA 和 ModelB 繼承。
這是一個經典的 object-oriented 設計。因為兩個 classes 共享一個通用特性(它們實現 fly 的方法),所以這個通用特性就被轉移到一個 base class 之中,并由兩個 classes 來繼承這個特性。這個設計使得通用特性變得清楚明白,避免了代碼重復,提升了未來的可擴展性,簡化了長期的維護——因為 object-oriented 技術,所有這些東西都受到很高的追捧。XYZ 航空公司應該引以為榮。
現在,假設 XYZ 公司的財富增長了,決定引進一種新機型,Model C。Model C 在某些方面與 Model A 和 Model B 不同。特別是,它的飛行不同。
XYZ 公司的程序員在 hierarchy(繼承體系)中增加了 Model C 的 class,但是由于他們匆匆忙忙地讓新的機型投入服務,他們忘記了重定義 fly function:
```
class ModelC: public Airplane {
... // no fly function is declared
};
```
于是,在他們的代碼中,就出現了類似這樣的東西:
```
Airport PDX(...); // PDX is the airport near my home
Airplane *pa = new ModelC;
...
pa->fly(PDX); // calls Airplane::fly!
```
這是一個災難:企圖讓一個 ModelC object 像一個 ModelA 或 ModelB 一樣飛行。這在旅行人群中可不是一種鼓舞人心的行為。
這里的問題并不在于 Airplane::fly 有缺省的行為,而是在于 ModelC 被允許不必明確說出它要做什么就可以繼承這一行為。幸運的是,“為 derived classes(派生類)提供缺省的行為,但是除非它們提出明確的要求,否則就不交給他們”是很容易做到的。這個訣竅就是切斷 virtual function(虛擬函數)的 interface(接口)和它的 default implementation(缺省實現)之間的聯系。以下用的就是這個方法:
```
class Airplane {
public:
virtual void fly(const Airport& destination) = 0;
...
protected:
void defaultFly(const Airport& destination);
};
void Airplane::defaultFly(const Airport& destination)
{
default code for flying an airplane to the given destination
}
```
注意 Airplane::fly 是被如何變成一個 pure virtual function(純虛擬函數)的。它為飛行提供了 interface(接口)。那個缺省的實現也會出現在 Airplane class 中,但是現在它是一個獨立的函數,defaultFly。像 ModelA 和 ModelB 這樣需要使用缺省行為的 Classes 只是需要在他們的 fly 的函數體中做一下對 defaultFly 的 inline 調用(但是請參見 Item 30 提供的關于 inline 化和 virtual functions(虛擬函數)的交互作用的信息):
```
class ModelA: public Airplane {
public:
virtual void fly(const Airport& destination)
{ defaultFly(destination); }
...
};
class ModelB: public Airplane {
public:
virtual void fly(const Airport& destination)
{ defaultFly(destination); }
...
};
```
對于 ModelC class,不可能在無意中繼承到不正確的 fly 的實現,因為 Airplane 中的 pure virtual(純虛擬)強制要求 ModelC 提供的它自己的 fly 版本。
```
class ModelC: public Airplane {
public:
virtual void fly(const Airport& destination);
...
};
void ModelC::fly(const Airport& destination)
{
code for flying a ModelC airplane to the given destination
}
```
這一方案并非十分安全(程序員還是能通過 copy-and-paste 使他們自己陷入麻煩),但是它比最初的設計更加可靠。至于 Airplane::defaultFly,它是 protected(保護的)是因為它完全是 Airplane 和它的 derived classes(派生類)的實現細節。使用飛機的客戶應該只在意它能飛,而不必管飛行是如何實現的。
Airplane::defaultFly 是一個 non-virtual function(非虛擬函數)這一點也很重要。這是因為 derived class(派生類)不應該重定義這個函數,這是一個在 Item 36 中專門介紹的原則。如果 defaultFly 是 virtual(虛擬的),你就會遇到一個循環的問題:如果某些 derived class(派生類)應該重定義 defaultFly 卻忘記了的時候會如何呢?
一些人反對為 interface(接口)和 default implementation(缺省實現)分別提供函數,就像上面的 fly 和 defaultFly 那樣。首先,他們注意到,這樣做會導致類似的相關函數名污染 class namespace(類名字空間)的問題。然而他們仍然同意 interface(接口)和 default implementation(缺省實現)應該被分開。他們是怎樣解決這個表面上的矛盾呢?通過利用以下事實:pure virtual functions(純虛擬函數)必須在 concrete derived classes(具體派生類)中被 redeclared(重聲明),但是它們也可以有它們自己的實現。以下就是 Airplane hierarchy(繼承體系)如何利用這一能力定義一個 pure virtual function(純虛擬函數):
```
class Airplane {
public:
virtual void fly(const Airport& destination) = 0;
...
};
void Airplane::fly(const Airport& destination) // an implementation of
{ // a pure virtual function
default code for flying an airplane to
the given destination
}
class ModelA: public Airplane {
public:
virtual void fly(const Airport& destination)
{ Airplane::fly(destination); }
...
};
class ModelB: public Airplane {
public:
virtual void fly(const Airport& destination)
{ Airplane::fly(destination); }
...
};
class ModelC: public Airplane {
public:
virtual void fly(const Airport& destination);
...
};
void ModelC::fly(const Airport& destination)
{
code for flying a ModelC airplane to the given destination
}
```
除了用 pure virtual function(純虛擬函數)Airplane::fly 的函數體代替了獨立函數 Airplane::defaultFly 之外,這是一個和前面的幾乎完全相同的設計。本質上,fly 可以被拆成兩個基本組件。它的 declaration(聲明)指定了它的 interface(接口)(這是 derived classes(派生類)必須使用的),而它的 definition(定義)指定它的缺省行為(這是 derived classes(派生類)可以使用的,但只是在他們明確要求這一點時)。將 fly 和 defaultFly 合并,無論如何,你失去了給予這兩個函數不同的保護層次的能力:原來是 protected 的代碼(通過位于 defaultFly 中實現)現在成為 public(因為它位于 fly 中)。
最后,我們看看 Shape 的 non-virtual function(非虛擬函數),objectID:
```
class Shape {
public:
int objectID() const;
...
};
```
當一個 member function(成員函數)是 non-virtual(非虛擬的)時,不應該指望它在 derived classes(派生類)中的行為會有所不同。實際上,一個 non-virtual member function(非虛擬成員函數)指定了一個 invariant over specialization(超越特殊化的不變量),因為不論一個 derived class(派生類)變得多么特殊,它都把它看作是不允許變化的行為。如下所指除的,
聲明一個 non-virtual function(非虛擬函數)的目的是 to have derived classes inherit a function interface as well as a mandatory implementation(使派生類既繼承一個函數的接口,又繼承一個強制的實現)。
你可以這樣考慮 Shape::objectID 的聲明,“每一個 Shape object 有一個產生 object identifier(對象標識碼),而且這個 object identifier(對象標識碼)總是用同樣的方法計算出來的,這個方法是由 Shape::objectID 的定義決定的,而且 derived class(派生類)不應該試圖改變它的做法。”因為一個 non-virtual function(非虛擬函數)被看作一個 invariant over specialization(超越特殊化的不變量),在 derived class(派生類)中他絕不應該被重定義,細節的討論參見 Item 36。
對 pure virtual,simple virtual,和 non-virtual functions 的聲明的不同允許你精確指定你需要 derived classes(派生類)繼承什么東西。分別是 interface only(僅有接口),interface and a default implementation(接口和一個缺省的實現),和 interface and a mandatory implementation(接口和一個強制的實現)。因為這些不同的聲明類型意味著根本不同的意義,當你聲明你的 member functions(成員函數)時你必須在它們之間仔細地選擇。如果你這樣做了,你應該可以避免由缺乏經驗的類設計者造成的兩個最常見的錯誤。
第一個錯誤是聲明所有的函數為 non-virtual(非虛擬)。這沒有給 derived classes(派生類)的特殊化留出空間;non-virtual destructors(非虛擬析構函數)尤其有問題(參見 Item 7)。當然,完全有理由設計一個不作為 base class(基類)使用的類。在這種情況下,一套獨享的 non-virtual member functions(非虛擬成員函數)是完全合理的。然而,更通常的情況下,這樣的類既可能出于對 virtual(虛擬)和 non-virtual functions(非虛擬函數)之間區別的無知,也可能是對 virtual functions(虛擬函數)的性能成本毫無根據的擔心的結果。事實是,幾乎任何作為 base class(基類)使用的類都會有 virtual functions(虛擬函數)(還是參見 Item 7)。
如果你關心 virtual functions(虛擬函數)的成本,請允許我提起基于經驗的 80-20 規則(參見 Item 30),在一個典型的程序中的情況是,80% 的運行時間花費在執行其中的 20% 的代碼上。這個規則是很重要的,因為它意味著,平均下來,你的函數調用中的 80% 可以被虛擬化而不會對你的程序的整體性能產生哪怕是最輕微的可察覺的影響。在你走進對“你是否能負擔得起一個 virtual function(虛擬函數)的成本”憂慮的陰影之前,應該使用一些簡單的預防措施,以確保你關注的是你的程序中能產生決定性不同的那 20%。
另一個常見的錯誤聲明所有的 member functions(成員函數)為 virtual(虛擬)。有時候這樣做是正確的—— Item 31 的 Interface classes(接口類)可以作為證據。然而,它也可能是缺乏表明態度的決心的類設計者的標志。某些函數在 derived classes(派生類)中不應該被重定義,而且只要在這種情況下,你都應該通過將那些函數聲明為 non-virtual(非虛擬)而明確地表達這一點。它不是為那些人服務的,他們假設如果他們只需花一些時間重定義你的所有函數,你的類就會被所有的人用來做所有的事情,如果你有一個 invariant over specialization(超越特殊化的不變量),請直說,不必害怕!
Things to Remember
Inheritance of interface(接口繼承)與 inheritance of implementation(實現繼承)不同。在 public inheritance(公開繼承)下,derived classes(派生類)總是繼承 base class interfaces(基類接口)。
Pure virtual functions(純虛擬函數)指定 inheritance of interface only(僅有接口被繼承)。
Simple (impure) virtual functions(簡單虛擬函數)指定 inheritance of interface(接口繼承)加上 inheritance of a default implementation(缺省實現繼承)。
Non-virtual functions(非虛擬函數)指定 inheritance of interface(接口繼承)加上 inheritance of a mandatory implementation(強制實現繼承)。
- Preface(前言)
- Introduction(導言)
- Terminology(術語)
- Item 1: 將 C++ 視為 federation of languages(語言聯合體)
- Item 2: 用 consts, enums 和 inlines 取代 #defines
- Item 3: 只要可能就用 const
- Item 4: 確保 objects(對象)在使用前被初始化
- Item 5: 了解 C++ 為你偷偷地加上和調用了什么函數
- Item 6: 如果你不想使用 compiler-generated functions(編譯器生成函數),就明確拒絕
- Item 7: 在 polymorphic base classes(多態基類)中將 destructors(析構函數)聲明為 virtual(虛擬)
- Item 8: 防止因為 exceptions(異常)而離開 destructors(析構函數)
- Item 9: 絕不要在 construction(構造)或 destruction(析構)期間調用 virtual functions(虛擬函數)
- Item 10: 讓 assignment operators(賦值運算符)返回一個 reference to *this(引向 *this 的引用)
- Item 11: 在 operator= 中處理 assignment to self(自賦值)
- Item 12: 拷貝一個對象的所有組成部分
- Item 13: 使用對象管理資源
- Item 14: 謹慎考慮資源管理類的拷貝行為
- Item 15: 在資源管理類中準備訪問裸資源(raw resources)
- Item 16: 使用相同形式的 new 和 delete
- Item 17: 在一個獨立的語句中將 new 出來的對象存入智能指針
- Item 18: 使接口易于正確使用,而難以錯誤使用
- Item 19: 視類設計為類型設計
- Item 20: 用 pass-by-reference-to-const(傳引用給 const)取代 pass-by-value(傳值)
- Item 21: 當你必須返回一個對象時不要試圖返回一個引用
- Item 22: 將數據成員聲明為 private
- Item 23: 用非成員非友元函數取代成員函數
- Item 24: 當類型轉換應該用于所有參數時,聲明為非成員函數
- Item 25: 考慮支持不拋異常的 swap
- Item 26: 只要有可能就推遲變量定義
- Item 27: 將強制轉型減到最少
- Item 28: 避免返回對象內部構件的“句柄”
- Item 29: 爭取異常安全(exception-safe)的代碼
- Item 30: 理解 inline 化的介入和排除
- Item 31: 最小化文件之間的編譯依賴
- Item 32: 確保 public inheritance 模擬 "is-a"
- Item 33: 避免覆蓋(hiding)“通過繼承得到的名字”
- Item 34: 區分 inheritance of interface(接口繼承)和 inheritance of implementation(實現繼承)
- Item 35: 考慮可選的 virtual functions(虛擬函數)的替代方法
- Item 36: 絕不要重定義一個 inherited non-virtual function(通過繼承得到的非虛擬函數)
- Item 37: 絕不要重定義一個函數的 inherited default parameter value(通過繼承得到的缺省參數值)
- Item 38: 通過 composition(復合)模擬 "has-a"(有一個)或 "is-implemented-in-terms-of"(是根據……實現的)
- Item 39: 謹慎使用 private inheritance(私有繼承)
- Item 40: 謹慎使用 multiple inheritance(多繼承)
- Item 41: 理解 implicit interfaces(隱式接口)和 compile-time polymorphism(編譯期多態)
- Item 42: 理解 typename 的兩個含義
- Item 43: 了解如何訪問 templatized base classes(模板化基類)中的名字
- Item 44: 從 templates(模板)中分離出 parameter-independent(參數無關)的代碼
- Item 45: 用 member function templates(成員函數模板) 接受 "all compatible types"(“所有兼容類型”)
- Item 46: 需要 type conversions(類型轉換)時在 templates(模板)內定義 non-member functions(非成員函數)
- Item 47: 為類型信息使用 traits classes(特征類)
- Item 48: 感受 template metaprogramming(模板元編程)
- Item 49: 了解 new-handler 的行為
- Item 50: 領會何時替換 new 和 delete 才有意義
- Item 51: 編寫 new 和 delete 時要遵守慣例
- Item 52: 如果編寫了 placement new,就要編寫 placement delete
- 附錄 A. 超越 Effective C++
- 附錄 B. 第二和第三版之間的 Item 映射