35.使公有繼承體現 “是一個” 的含義。
共同擁有繼承意味著 “是一個”。如 class B:public A。 說明型別B的每個物件都是一個型別A的物件,A比B具有更廣泛的概念。而B表示一個更特定的概念。
在C++中不論什麼一個引數為基類的函式都能夠實際取一個派生類的物件,僅僅有共同擁有繼承會如此。對於共同擁有繼承,如AB。若有兩個函式 一個函式為 void fun1(A &a);還有一個函式為void fun2(B& b);則對於AB的兩個物件a。和b。對於 fun1(a)和fun2(b和fun1(b))都是正確的。fun2(a)是錯誤的。注意僅僅有共同擁有繼承才有這個特性,對於私有繼承會與此不同。並且這是說 B的物件 "是一個“ A的物件,可是B的陣列並非一個A的陣列。
使用公有繼承常常遇到的問題是對基類適用的規則並不適用於派生類。但公有繼承又要求對基類物件適用的不論什麼東西都適用於派生類物件,使用公有繼承會導致一些錯誤的設計。
如對於企鵝與鳥,鳥是基類,其有嘴,翅膀等資料成員。另一個飛的成員函式virtual void fly();一開始你覺得鳥有一些屬性。且鳥會飛,然後你有覺得企鵝公有繼承於鳥,即企鵝是一種鳥,可是問題出現了,企鵝不會飛。
讓企鵝直接公有繼承於鳥類。這是一個錯誤的設計。所以你想去改進它,在依舊使用公有繼承的前提下。
1.世上有非常多鳥不會飛,於是你將鳥類分成了兩種,FlyingBird 和NoFlyingBird,分成會飛和不會飛兩種鳥類,這兩種鳥類都公有繼承於鳥類,而企鵝公有繼承於NoFlyingBird。
2.企鵝中依舊有fly()這個函式,可是又一次定義了這個fly函式。使之產生一個執行時錯誤,使企鵝是鳥,企鵝能飛,可是讓企鵝飛的這個操作是錯誤的。
這是一個執行時才幹檢測的錯誤。
當利用一些知識和常識設計一些類並使用公有繼承時,可是公有繼承卻沒那麼有效,由於最關鍵的問題是基類中的規則要相同適用於派生類物件,而我們想要用繼承實現的物件卻有兩者不同的規則。而對於這種情況,一般要用”有一個“ 和”用。。
。來實現“這兩種關係來實現。
36.區分介面繼承和實現繼承。
首先,介面是放在public中給外部呼叫的。而實現是隱藏在private中的內部邏輯。對於類的繼承,有時希望派生類僅僅繼承成員函式的介面。有時派生類同一時候繼承函式的介面和實現。且同意派生類改寫實現,有時派生類同一時候繼承類的介面與實現。可是不同意改動不論什麼東西。
純虛擬函式必需要在詳細實現類中又一次宣告,它們在抽象類中往往未定義。定義純虛擬函式的目的在於使派生類只 繼承函式的介面。也就是第一種情況,這樣的情況非常easy理解。
可是純虛擬函式事實上是能夠提供定義的。
對於另外一種和第三種情況,繼承函式的介面和實現,一般使用虛擬函式來實現。
而須要改進的地方是。對於一個基類中的虛擬函式,其有一定的實現。而派生類能夠繼承這種介面和實現,既能夠直接繼承基類中這個介面,也能夠重寫這個介面的實現。
這樣非常科學,可是又要一個問題,當一個新的派生類繼承這個基類時。因為這個類中使用虛擬函式做介面,導致新的程式猿忘記了又一次宣告這個虛擬函式並給予新的實現邏輯而去錯誤的使用虛擬函式中的預設邏輯而造成了錯誤。為了提供更安全的基類,使用純虛擬函式做介面,讓純虛擬函式有自己預設實現,在派生類繼承時。直接呼叫基類純虛擬函式的實現:
class A{
public:
virtual void fun() const = 0;
};
void A::fun() const{
cout<<"Class A"<<endl;
}
class B:public A{
public:
virtual void fun() const;
};
void B::fun() const{
A::fun();
}
如上所看到的,使用一個純虛擬函式。可是帶有預設實現,而派生類繼承時就必須又一次宣告這個純虛擬函式,而對於要呼叫基類的預設實現時,除了上面直接呼叫基類的這個純虛擬函式外,還能夠通過在基類中的protected中設定一個預設的實現函式,如 void defaultFun() const。而派生類會繼承這個預設實現,然後在派生類的又一次定義的虛擬函式中呼叫這個預設的實現函式就可以。
這個情況事實上就是另外一種情況,繼承函式的介面和實現,且可以改動實現。一般使用虛擬函式。可是使用帶預設操作的純虛擬函式會更加安全。安全是一個非常重要的問題,假設不考慮安全性,非常多在Effective C++這本書中討論的問題都是沒有意義的,由於假設你明確之前程式的設定。就知道哪些事情該做,哪些事情不該做,就不會去犯一些錯誤,可是對於一個程式的開發。不是有一個人完畢的。當你理解自己的設定時,別人卻不知道,維護你程式碼的人任意的做一些他們覺得應該可以做到的安全的事,卻由於你之前考慮的不周全而使這些行為極度不安全。所以要認真考慮安全性的問題。寫出儘可能完美安全的程式碼。
對於第三種情況,宣告非虛擬函式。目的在於使派生類繼承函式的介面和強制性實現,又因為不應該在派生類中又一次宣告和定義基類的非虛擬函式,所以不會改動非虛擬函式的實現的。
所以,要理解純虛擬函式,簡單虛擬函式和非虛擬函式宣告和功能上的差別。不用操心虛擬函式的效率問題,由於這真的是小問題,全部基類都應該虛擬函式。
一些函式不應該在派生類中又一次定義就要將其定義為非虛擬函式。
37.決不要又一次定義繼承而來的非虛擬函式。
首先,對於又一次定義繼承的非虛擬函式,稱為對這個函式的隱藏。這是一種不經常使用的東西,正是由於有這個設定,絕不又一次定義繼承而來的非虛擬函式。
這樣做的原因也是非常easy理解的,也是多型的長處:
class A{
public:
void fun() const{
cout<<"Class A"<<endl;
}
};
class B:public A{
public:
void fun() const{
cout<<"Class B"<<endl;
}
};
int main(){
B* b = new B();
A* a = b;
b->fun();
a->fun();
對於以上程式碼,對同一個物件,也就是b指向的物件,當將其轉換為基類指標後,因為其為靜態繫結的,其所指向的函式不同,獲得了不同的結果。而多型時動態繫結,指向的函式通過虛指標指向同樣的地址。結論是,對於類B的物件,其又一次定義的函式fun()被呼叫時。其行為是不確定的,而決定因素與物件本身沒有關係,而取決於指向它的指標的宣告型別,引用也會和指標表現出這種異常行為,這種行為是不合理的。
而從理論上來考慮。對於公有繼承意味著 ”是一個“,對於B中又一次定義了A中的實現後,B就不”是一個“ A了。