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