Effective C++學習筆記 第六彈 35-41

 

條款35:確定你的public inheritance,模塑出“isa”的關係

一、企鵝和鳥

class Bird{

public:

    virtual void fly();//鳥可以飛

    ...

};

class Penguin:public Bird{ //企鵝是一種鳥

  ...

};

以上繼承體系說企鵝可以飛;其實當我們說鳥可以飛的時候並不是指所有鳥都可以飛。

二、長方形和矩形

class Rectangle{

public:

    virtual void setHeight(int newHeight);

    virtual void setWidth(int newWidth);

    virtual int height() const;

    virtual int width() const;

    ....

};

void makeBigger(Rectangle& r)//增加r的面積

{

    int oldHeight = r.height();

    r.setWidth(r.width()+10);

    assert(r.height() == oldHeight);      //判斷r的高度是否未曾改變

}

class Square:public Rectangle { .... };

Square s;

...

assert(s.Width() == s.height());

makeBigger(s);

assert(s.width() == s.height());

三個assert出現無法調解的情況。

條款36:區分接口繼承(interface inheritance)和實現繼承(implementation inheritance)

一、Member function的接口總是會被繼承,所以任何事情只要對base class而言爲真,就一定對其derived classes爲真。

二、聲明一個純虛擬函數的目的是爲了讓derived classes只繼承其接口

三、聲明一般(非純)虛擬函數的目的,是爲了讓derived classes繼承該函數的接口和缺省行爲。

       有時候你可能忘了重寫虛擬方法,導致子類繼承了基類的方法而出現bug。這時你可以用這種機制:

class Airplane{

public:

    virtual void fly(const Airport& destination) = 0;

    ....

protected:

    void defaultFly(const Airport& destination)

    { default code for flying ; }

};

class Model:public Airplane{

public:

    virtual void fly(const Airport& destination)

    { defaultFly(destination); }

};

這樣子類就必須要求給出fly的重寫,而且仍然可以顯示調用默認的fly函數。

同時,利用“純虛擬函數必須在subclasses中重新聲明,但是純虛擬函數也可以擁有自己的定義”這條規則來更好的實現上面的機制

class Base
{
public:
 virtual void print() = 0;
};

void Base::print()
{
 cout<<"Base print"<<endl;
}

class Derived:public Base
{
public:
 virtual void print()
 {
  Base::print();
 }
};

四、聲明非虛擬函數的目的是爲了令derived classes繼承函數的接口和實現

可能會遇到的兩個錯誤:

1、第一個錯誤是將所有函數聲明爲非虛擬函數,這使得derived classes沒有富裕空間進行特殊化工作。

2、另一個錯誤是將所有member function都聲明爲虛擬函數,不但降低了效率還使得函數太過泛泛。

條款37:絕對不要重新定義繼承而來的非虛擬函數

class Base
{
public:
    void mf()
    { cout<<"Base::mf()"<<endl;}
};

class Derived:public Base
{
public:
 void mf()
 { cout<<"Derived::mf()"<<endl;}
};

void main()
{
 Derived x;
 Base *p = &x;
 p->mf();
 Derived *pp = &x;
 pp->mf();
}

兩次調用mf()都不同 因爲兩個mf()都是靜態綁定的,不同過虛擬表,哪個類的指針就調用哪個類的函數。另一方面,如果是動態綁定(虛函數),不管是p還是pp都會調用Derived的mf(),因爲p和pp真正指向的都是一個D對象。

結論:

1、因爲D是一個B,所以B的非虛擬方法都繼承下來,如果D的mf真的很B的mf不同,那麼“每個D都是一種B”就不爲真了,D不應該是B的子類;

2、如果D確實是B的子類,爲了反映出多態性,mf應該聲明爲虛擬函數;

兩種解釋都支持條款37的觀點

條款38:絕對不要重新定義繼承而來的缺省參數值


侷限於繼承一個帶有缺省參數值的虛擬函數

Shape *ps;

Shape *pc = new Circle;

Shape *pr = new Rectangle;

ps pc pr的靜態型別都是Shape* 但是動態型別就不一樣了,pc是Circle*而pr是Rectangle*,ps並沒有動態型別,因爲它尚未指向任何對象。

假如 ps = pc;那是ps的動態型別也變成Circle。虛擬函數系動態綁定而來,調用哪個函數取決於對象的動態型別。

但是當 pr->Draw();時,會調用Rectangle的Draw(),但是缺省參數值來自於Shape Class的RED,爲什麼呢?因爲缺省參數是靜態綁定的,他依據的是靜態型別。C++這樣做,是爲了效率。

條款39:避免在繼承體系中做向下轉型(cast down)動作

 

先來看傳統方式,

for(list<BankAccount*>::interator p = allAccounts.begin();

     p != allAccounts.end();

     ++p ) {

   SavingsAccount *psa;

   CheckingAccount *pca;

    if (psa = dynamic_cast<SavingsAccount*>(*p)) {

         psa->creditInterest();

    }

    else if  (pca = dynamic_cast<CheckingAccount*>(*p)) {

         pca->creditInterest();

    }

    else  {

         error("Unknown account type !");

    }

}

必然導致if-then-else程序風格,如果使用static_cast強制轉換效果更差,當用dynamic_cast運用到指針上時,如果成功,那麼指針的動態型別與其轉型目標一致;如果失敗,會傳回null指針。

而解決該機制的最佳辦法就是將轉型動作以虛擬函數的調用取代。



條款40:通過layering技術來模塑has-a或is-implemented-in-terms-of的關係

一、模塑has-a技術

class Address { ...  };

class PhoneNumber { ... };

class Person {

public:

    ...

private:

    string name;

    Address address;

    PhoneNumber voiceNumber;

    PhoneNumber faxNumber;

};

二、模塑is-implemented-in-terms-of技術(根據某物實現)

template<class T>

class Set {

public:

     bool member(const T& item) const;

     void insert(const T& item);

     void remove(const T& item);

     int cardinality() const;

private:

     list<T> rep;

};

這個set類底層實際是通過list實現的。

條款41:區分inheritance和templates

 一、template應該用來產生一羣classes,其中對象型別不會影響class的函數行爲

如Stack類

template<class T> class Stack {

};

無論T是什麼型別,都不影響棧的函數行爲。

二、inheritance應該用於一羣classes上,其中對象型別會影響class的函數行爲

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章