Effective C++讀書筆記(條款35-40),effective35-40
(六).繼承與物件導向設計
____________________________________________________________________________________________________________________________________
條款35:考慮virtual函數以外的其他選擇
#1.virual函數的四個替代方案:
(1).使用non-virtual interface(NVI)手法,那是 Template Method 設計模式
的一種特殊形式。它以public non-virtual 成員函數包裹較低訪問性(private
或 protected)的virtual函數。
(2).將virtual函數替換為 "函數指標成員變數", 這是Strategy設計模式的一種
分解表現方式。
(3).以tr1::function 成員變數替換 virtual 函數,因而允許使用任何可調用物
(callable entity) 搭配一個相容於需求的簽名式。這也是 Strategy設計模式的
某種形式。
(4).將繼承體系內的 virtual 函數替換為另一個繼承體系內的virtual 函數。
這是Strategy 設計模式的傳統實現手法。
<1>.由Non-Virtual Interface手法實現Template Method模式
//考慮以下代碼:class GameCharacter{public: int healthValue() const //derived classes不重新定義它 { //見36條款 ... //做一些事前工作,詳下 int retVal = doHealthValue(); //做真正的工作 ... //做一些事後工作 } ...private: virtual int doHealthValue() const //derived classes可重新定義它 { ... }};//我們稱healthValue函數為virutal函數doHealthValue的外覆器,//它提供了完整實現,而virtual函數則提供了具體實現,//通過NVI手法,我們可以更專註於事件如何被具體完成
<2>.由Function Pointers 實現 Strategy 模式
//考慮以下代碼:class GameCharacter; //前置聲明(forward declaration)//以下函數是計算健康指數的預設演算法int defaultHealthCalc(const GameCharacter& gc);class GameCharacter{public: typedef int (*HealthCalcFunc)(const GameCharacter&); explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf){} int healthValue() const {return healthFunc(*this);} ...private: HealthCalcFunc healthFunc;};//同一人物類型之不同實體可以有不同的健康計算函數,例如:class EvilBadGuy:public GameCharacter{public: explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc) : GameCharacter(hcf) {...} ...};int loseHealthQuickly(const GameCharacter&); //健康Function Compute函數1int loseHealthSlowly(const GameCharacter&); //健康Function Compute函數2EvilBadGuy ebg1(loseHealthQuickly); //相同類型的人物搭配EvilBadGuy ebg2(loseHealthSlowly); //不同的健康計算方式//defualtHealthCalc並未訪問EvilBadGuy的non-public成分,//若要訪問non-public成分,需使其成為friends,因此可能//降低class的封裝性。//運用函數指標替換virtual函數,其優點(像是"每個對象//可各自擁有自己的健康計算函數”和“可在運行期改變計算//函數”)是否足以彌補缺點(例如可能降低GameCharacter//的封裝性),是你必鬚根據設計情況而抉擇的。
<3>.由tr1::function完成Strategy模式
//考慮以下代碼:class GameCharacter;int defaultHealthCalc(const GameCharacter&);class GameCharacter{public: //HealthCalcFunc可以是任何 “可調用物” (callable entity), //可被調用並接受任何相容於 GameCharacter 之物,返回任何 //相容於 int 的東西。詳下。 typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc; explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf) {} int healthValue() const {return healthFunc(*this);} ...private: HealthCalcFunc healthFunc;};//如今的tr1::function相當於一個指向函數的泛化指標,//它在“指定健康計算函數”這件事上具有更驚人的彈性:short calcHealth(const GameCharacter&); //健康計算函數 //注意其傳回型別為non-intstruct healthCalculator{ //為計算健康而設計的函數對象 int operator()(const GameCharacter&) const {...}};class GameLevel{public: float health(const GameCharacter&)const; //成員函數,用以計算健康; ... //注意其non-int 傳回型別};class EvilBadGuy:public GameCharacter{ //同前 ...};class EyeCandyCharacter:public GameCharacter{ //另一個人物類型; ... //假設其建構函式與}; //EvilBadGuy相同EvilBadGuy ebg1(calcHealth); //人物1,使用某個 //Function Compute健康函數 EveCandyCharacter ecc1(HealthCalculator()); //人物2,使用某個 //函數對象計算健康函數GameLevel currentLevel;...EvilBadGuy ebg2( //函數3,使用某個std::tr1::bind(&GameLevel::health, //成員Function Compute健康函數 currentLevel, _1,) //詳見以下);//使用tr1::function的好處是允許使用任何可調用物,//只要其調用形式相容於簽名式
<4>.由古典的 Strategy 模式
//考慮以下代碼:class GameCharacter;class HealthCalcFunc{public: ... virtual int calc(const GameCharacter& gc) const { ... } ...};HealthCalcFunc defaultHealthCalc;class GameCharacter{public: explicit GameCharacter(HealthCalcFunc* phcf = &defaultHealthCalc) : pHealthCalc(phcf) {} int healthValue() const { return pHealthCalc->calc(*this); } ...private: HealthCalcFunc* pHealthCalc;};//這個解法的吸引力在於,它提供“將一個既有的健康演算法納入//使用”的可能性---只要為HealthCalcFunc 繼承體系添加一個//derived class 即可。
____________________________________________________________________________________________________________________________________
條款36:絕不重新定義繼承而來的non-virtual函數
#1.絕不重新定義繼承而來的non-virtual函數
理由(1):non-virtual及重新定義下的函數都是靜態繫結,
調用它們的結果不是我們所預期的。
class B{public: void mf(); ...};class D:public B {public: void mf(); ...};D x;B* pB = &x;D* pD = &x;pB->mf(); //調用B::mf()pD->mf(); //調用D::mf()//對於調用同一個對象x,卻出現了不同行為,//這不是我們所希望的,與其如此重新定義,//還不如讓其成為virtual函數
理由(2).繼承下來的函數分為virtual和non-virtual, virtual意味著
其實現容易被替換,因此實現動態綁定,而non-virtual意味著其不變性
淩駕於特異性,因繼承其實現所以應用於靜態繫結。
____________________________________________________________________________________________________________________________________
條款37:絕不重新定義繼承而來的預設參數值
#1.絕不重新定義繼承而來的預設參數值,因為預設參數值都是靜態繫結,
而virtual 函數---你唯一應該覆寫的東西---卻是動態綁定。
//一個用以描述幾何形狀的classclass Shape{public: enum ShapeColor { Red, Green, Blue }; //所有形狀都必須提供一個函數,用來描繪自己 virtual void draw(ShapeColor color = Red) const = 0; ...};class Rectangle:public Shape{public: //注意,賦予不同的預設參數值。真實糟糕! virtual void draw(ShapeColor color = Green) const; ...};class Circle:public Shape{public: virtual void draw(ShapeColor color) const //請注意,以上這麼寫則當客戶以對象調用此函數,一定要指定參數值。 //因為靜態繫結下這個函數並不從其base 繼承預設參數值。 //但若以指標(或reference) 調用此函數,可以不指定其參數值, //因為動態綁定下這個函數會從其base繼承預設參數值 ...};//現考慮以下指標:Shape* ps; //靜態類型為Shape*Shape* pc = new Circle; //靜態類型為Shape*Shape* pr = new Rectangle; //靜態類型為Shape*pc->draw(Shape::Red); //調用Circle::draw(Shape::Red)pr->draw(Shape::Red); //調用Rectangle::draw(Shape::Red)//此代碼的virtual函數是動態綁定,而預設參數值卻是靜態繫結//這造成了一種奇怪的調用方式,不能統一調用,而編譯器之所以//不為預設參數值動態綁定的原因是運行期效率。//那麼我們是否可以為derived指定同樣的預設值呢?,就像這樣:class Rectangle:public Shape{public: virtual void draw(ShapeColor color = Red) const; ...};//答案是否,理由很簡單,這造成了我們的代碼重複,//而且帶有相依性,要是哪天修改了預設值就要動輒牽動全身了。//一種更好的做法是讓non-virtual指定預設值來代替工作:class Shape{public: enum draw(ShapeColor color = Red) const //如今它是一個non-virtual { doDraw(color); //調用一個virtual } ...private: virtual void doDraw(ShapeColor color)const = 0; //真正的工作在此處完成};class Rectangle:public Shape{public: ...private: virtual void doDraw(ShapeColor color)const; //無須指定預設值 ...};//由於non-virtual函數絕不被derived class覆寫,這個設計很清楚地使得//draw 函數的color 預設參數值總是為 true.
____________________________________________________________________________________________________________________________________
條款38:通過複合塑模出 has-a 或 “根據某物實現出”
#1.複合有雙層含義,在應用域(application domain), 複合意味 has-a
(有一個)。在實現域(implementation domain), 複合意味
is-implemented-in-terms-of (根據某物實現出)。
____________________________________________________________________________________________________________________________________
條款39:明智而審慎地使用private繼承
#1.Private 繼承意味 is-implemented-terms-of(根據某物實現出)。它
通常比複合(composition)的層級低。但是當 derived class需要訪問
protected base class 的成員,或需要重新定義繼承而來的 virtual 函數
時,這麼設計是合理的。
//例如我們要使用Timer中的功能,我們可以implemented-in-terms-ofclass widget:private Timer{private: //private許可權:避免客戶調用 virtual void onTick() const; //重新定義我們所需的onTick函數功能 ...};
#2.一種替代private繼承的設計是使用複合class,其用法如下:
class Widget{private: class WidgetTimer:public Timer{ public: virtual void onTick() const; ... }; WidgetTimer timer; ...};//這種設計的好處是://(1).它可以擁有自己的derived class.//(2).將編譯依存性降至最低(分離,相依於聲明式)
#3.獨立非附屬對象的大小一定不為零,但作為附屬對象,它存在
EBO(Empty base optimization),即空白基類最佳化,可以使其
大小為零。
例如:
class Empty{};class HoldsAnInt:private Empty{private: int x;}//幾乎可以確定,sizeof(HoldsAnInt) = sizeof(int)
#4.和複合(compresition)不同,private繼承可以造成empty base最佳化。
這對致力於“對象尺寸最小化”的程式庫開發人員而言,可能很重要,此時
#2的替代設計就不能再滿足了。
____________________________________________________________________________________________________________________________________
條款40:明智而審慎地使用多重繼承
#1.virtual 繼承會增加大小,速度,初始化等成本,因此非必要時不要使用
virtual繼承。同時它也意味著,virtual base class 資料越少,其使用
價值越高,因此應儘可能避免在其中放置資料。
#2.當涉及繼承組合時,多重繼承便發揮了其正當用途。例如"public繼承某個
Interface class" 和 “private 繼承某個協助實現的 class" 的兩兩組合:
class IPerson{ //該class指出需實現介面public: virtual ~IPerson(); virtual std::string name() const = 0; virtual std::string birthDate() const = 0; virtual std::string birthDate() const = 0;};class DatebaseID{...}; //稍後被使用,細節不重要。class PersonInfo{ //這個class有若干有用函數public: //可用以實現IPerson介面。 explicit PersonInfo(DatabaseID pid); virtual ~PersonInfo((); virtual const char* theName() const; virtual const char* theBirthDate() const; virtual const char* valueDelimOpen() const; virtual const char* valuedelimClose() const; ...};class CPerson:public IPerson, private PersonInfo{ //注意,多重繼承public: explicit CPerson(DatabaseID pid):PersonInfo(pid){} virtual std::string name() const //實現必要的IPerson函數 {return PersonInfo::theName();} virtual std::string birthDate() const //實現必要的IPerson函數 {return PersonInfo::theBirthDate();}private: const char* valueDelimOpen() const {return "";} //重新定義繼承而來的 const char* valueDelimClose() const {return "";}//virtual ”界限函數“};
____________________________________________________________________________________________________________________________________