天天看點

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:絕對不要重新定義繼承而來的預設參數值

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

局限于繼承一個帶有預設參數值的虛拟函數

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)動作

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

先來看傳統方式,

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指針。

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

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

條款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的函數行為