Effective C++讀書筆記(條款35-40),effective35-40

來源:互聯網
上載者:User

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 ”界限函數“};

____________________________________________________________________________________________________________________________________

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.