標籤:導致 動態 pre cpp 情況 產生 個人 轉換 虛函數
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了。
Effective C++ 35,36,37