1.確定你的public繼承塑造出is-a關系
public繼承意味著is-a.適用于base classes身上的每一件事情一定也適用于derived classes身上,因為每一個derived classes對象也都是一個base class對象,反過來不成立。
2.避免隱藏繼承而來的名稱
c++名稱查找不考慮類型,只考慮名稱。
class Base { private:int x; public:virtual void mf1() = 0;virtual void mf1(int){}virtual void mf2(){}void mf3(){}void mf3(double){} }; class Derived :public Base { public:virtual void mf1(){}void mf3(){}void mf4(){} }; /* base class內所有名為mf1和mf3的函數都被derived class內的mf1和mf3函數遮掩掉 函數隱藏只與函數名稱有關,與函數的參數類型、是否virtual無關,注意此處是靜態綁定
若通過指針或引用來調用虛函數,此時是動態綁定,不會發生函數隱藏 */ int main() {Derived d;int x = 1;d.mf1();//ok//d.mf1(x);//falsed.mf2();//okd.mf3();//ok//d.mf3(x);//false system("pause");return 0; }
class Base { private:int x; public:virtual void mf1() = 0;virtual void mf1(int){}virtual void mf2(){}void mf3(){}void mf3(double){} };class Derived :public Base { public:using Base::mf1;using Base::mf3;virtual void mf1(){}void mf3(){}void mf4(){} };/* 如果你繼承base class并且base class中有重載函數,而你又希望在派生類中重新定義其中一部分,那么你必須為那些 原本會被隱藏的每個名稱引入一個using聲明式,否則某些你希望繼承的名稱會被隱藏。using聲明式會令繼承而來的基類中某
給定名稱之所有同名函數在derived class中都可見 */ int main() {Derived d;int x = 1;d.mf1();//okd.mf1(x);//okd.mf2();//okd.mf3();//okd.mf3(x);//ok system("pause");return 0; }
3.區分接口繼承和實現繼承
- 聲明一個pure virtual函數的目的是為了讓derived classes只繼承函數接口
通常純虛函數沒有定義,但是可以為純虛函數提供定義,調用它的唯一途徑是調用時明確指出其class名稱
class Shape { public:virtual void draw() const = 0{}virtual void error(const string& msg);int objectID() const; };class Rectangle:public Shape { public:virtual void draw() const{} };class Ellipse:public Shape { public:virtual void draw() const {} };int main() {//Shape *ps = new Shape;//error 抽象類不能生成實例Shape *ps1 = new Rectangle;ps1->draw();Shape *ps2 = new Ellipse;ps2->draw();ps1->Shape::draw();ps2->Shape::draw();system("pause");return 0; }
- 聲明impure virtual函數的目的,是讓derived class繼承該函數的接口和缺省實現
如果有的派生類只想繼承接口但忘記重新定義該虛函數,這樣一來就會使用基類缺省實現,出錯:將接口和缺省實現分開,派生類如果想使用缺省實現,需要去顯式調用
方案一:將接口定義為純虛函數,缺省實現定義為non-virtual函數,派生類若想使用缺省實現,需要在繼承而來的純虛函數中調用該non-virtual函數,派生類若只想繼承接口,此時不會忘記重新定義該虛函數了,因為不重新定義的話就是抽象類,無法實例化
方案二:純虛函數必須在derived class中重新定義,但基類中也可以為純虛函數提供定義,該定義作為缺省實現。派生類若想使用缺省實現,需要在在繼承而來的純虛函數中通過基類名來顯式調用上述函數定義
- 聲明non-virtual函數的目的是為了令derived class繼承函數的接口及一份強制性實現
4.考慮virtual函數以外的其他選擇
- NVI手法:令用戶通過public non-virtual成員函數間接調用private virtual函數
- 由Function Pointer實現strategy模式
運用函數指針(作為類的成員變量)替換virtual函數,優點是每個對象可各自擁有自己的對應函數及可在運行期改變該函數,缺點是可能會降低類的封裝性
- 由tr1::function完成strategy模式
- 古典的strategy模式
5.絕不重新定義繼承而來的non-virtual函數(設計理念)
6.絕不重新定義繼承而來的缺省參數值
虛函數是動態綁定的,而缺省參數值是靜態綁定的。
class Shape { public:enum ShapeColor{Red,Green,Blue};virtual void draw(ShapeColor color = Red) const = 0; };class Circle :public Shape { public:/*當用戶以對象調用此函數,一定要指定參數值。因為靜態綁定下這個函數并不從其基類繼承缺省參數值。但若以指針或引用調用此函數,可以不指定參數值,因為動態綁定下這個函數會從其基類繼承缺省參數值*/virtual void draw(ShapeColor color) const; };
7.通過復合composition塑造出has-a或"根據某物實現出"
復合:某種類型的對象內含別的類型的對象,復合意味著has-a或is-implemented-in-terms-of
8.明智地使用private繼承
- 如果class之間的繼承關系是private,編譯器不會自動將一個derived class對象轉換為一個base class對象(將派生類實參傳遞給基類形參,編譯出錯)
- 由private base class繼承而來的所有成員,在derived class中都會變成private屬性,縱使它們在base class中原本是protected或public屬性,即派生類對象不能調用基類方法
private繼承意味著implemented-in-terms-of,private繼承純粹只是一種實現技術。
9.明智地使用多重繼承
未完待續