《Effective C++ 中文版 第三版》讀書筆記
身為 class 設(shè)計(jì)者,有時(shí)候你希望 derived class 只繼承成員函數(shù)的接口(也就是聲明):有時(shí)候你又希望 derived class 同時(shí)繼承函數(shù)的接口和實(shí)現(xiàn),但又希望能夠覆寫(override)它們所繼承的實(shí)現(xiàn):又有時(shí)候你希望 derived class 同時(shí)繼承函數(shù)的接口與實(shí)現(xiàn),并且不允許覆寫任何東西。
讓我們考慮一個(gè)展現(xiàn)繪圖程序中各種幾何形狀的 class 繼承體系:
class Shape {
public:
virtual void draw() const = 0;
virtual void error(const std::string& msg);
int objectID() const;
};
class Rectangle : public Shape {...};
class Ellipse : public Shape {...};
Shape 的 pure virtual 函數(shù) draw 使它成為一個(gè)抽象 class。Shape 強(qiáng)烈影響了所有以 public 形式繼承它的 derived classes。因?yàn)椋?/p>
成員函數(shù)的接口總是會(huì)被繼承。
首先考慮 pure virtual 函數(shù) draw:pure virtual 函數(shù)有兩個(gè)最突出的特性:它們必須被任何 “繼承了它們” 的具象 class 重新聲明,而且它們?cè)诔橄?class 中通常沒(méi)有定義。所以:
聲明一個(gè) pure virtual 函數(shù)的目的是為了讓 derived class 只繼承函數(shù)接口。Shape::draw 的聲明式乃是對(duì)具象 derived class 設(shè)計(jì)者說(shuō) “你必須提供一個(gè) draw 函數(shù),但我不干涉你怎么實(shí)現(xiàn)它”
令人意外的是,我們竟然可以為 pure virtual 函數(shù)提供定義。但調(diào)用它的唯一途徑是 “調(diào)用時(shí)明確指出其 class 名稱”:
Shape* ps = new Shape; // wrong! abstract class Shape
Shape* ps1 = new Rectangle;
ps1->draw(); // Rectangle::draw()
Shape* ps2 = new Ellipse;
ps2->draw(); // Ellipse::draw()
ps1->Shape::draw();
ps2->Shape::draw();
它可以提供一種機(jī)制,為簡(jiǎn)樸 impure virtual 函數(shù)提更平常更安全的缺省實(shí)現(xiàn)。
impure virtual 函數(shù)會(huì)提供一份實(shí)現(xiàn)代碼,derived classes 可能覆寫(override)它:
生命簡(jiǎn)樸的(非純)impure virtual 函數(shù)的目的,是讓 derived class 繼承該函數(shù)的接口和缺省實(shí)現(xiàn)。
考慮 Shape::error 這個(gè)例子。
其接口表示,每個(gè) class 都必須支持一個(gè) “當(dāng)遇上錯(cuò)誤時(shí)可調(diào)用” 的函數(shù),但每個(gè) class 可自由處理錯(cuò)誤。如果某個(gè) class 不想針對(duì)錯(cuò)誤作出任何特殊行為,可以退回到 Shape class 提供的缺省錯(cuò)誤處理行為。
但是允許 impure virtual 函數(shù)同時(shí)指定函數(shù)聲明和函數(shù)缺省行為,卻有可能造成危險(xiǎn)。考慮 xyz 航空公司設(shè)計(jì)的繼承體系。該公司只有 A 和 B 兩種飛機(jī),相同方式飛行:
class Airport {...};
class Airplane {
public:
virtual void fly(const Airport& destination);
...
};
void Airplane::fly(const Airport& destination)
{
//缺省代碼,將飛機(jī)飛至指定目的地
}
class ModelA: public Airplane {...};
class ModelB: public Airplane {...};
為表示所有飛機(jī)都一定能飛,并闡明 “不同型飛機(jī)原則上需要不同的 fly 實(shí)現(xiàn)” Airplane::fly 被聲明為 virtual。然而為了避免在 ModelA 和 ModelB 中撰寫相同代碼,缺省飛行行為有 Airplane::fly 提供,它同時(shí)被 ModelA 和 ModelB 繼承。
現(xiàn)在,xyz 決定購(gòu)買一種新式 C 型飛機(jī),這種飛機(jī)的飛行方式不同。
xyz 的程序員在繼承體系中對(duì) C 型飛機(jī)添加了一個(gè) class,但由于他們急著讓新飛機(jī)上線服務(wù),忘了重新定義器 fly 函數(shù):
class ModelC: public Airplane {...};
然后在代碼中有一些諸如此類的動(dòng)作:
Airport PDX(...);
Airplane* pa = new ModelC;
...
pa->fly(PDX);
這將釀成大災(zāi)難:這個(gè)程序試圖用 ModelA 和 ModelB 的飛行方式來(lái)飛 ModelC。
問(wèn)題不在 Airplane::fly() 有缺省行為,而在于 ModelC 在未明白說(shuō)出 “我要” 的情況下就繼承了該缺省行為。我們可以做到 “提供缺省實(shí)現(xiàn)給 derived classes,但除非它們明確要求,否則免談”。此間伎倆在于切斷 “virtual 函數(shù)接口” 和其 “缺省實(shí)現(xiàn)” 之間的連接。下面是一種做法:
class Airplane {
public:
virtual void fly(const Airport& destination) = 0;
...
protected:
void defaultFly(const Airport& destination);
};
void Airplane::defaultFly(const Airport& destination)
{
//缺省行為,將飛機(jī)飛至目的地
}
fly 已被改成為一個(gè) pure virtual 函數(shù),只提供飛行接口。缺省行為以 defaultFly 出現(xiàn)在 Airplane class 中。若想使用缺省實(shí)現(xiàn)(例如 ModelA 和 ModelB),可以在 fly 中對(duì) defaultFly 做一個(gè) inline 調(diào)用:
class ModelA: public Airplane {
public:
virtual void fly(const Airport& destination)
{
defaultFly(destination);
}
...
};
class ModelB: public Airplane {
public:
virtual void fly(const Airport& destination)
{
defaultFly(destination);
}
...
};
現(xiàn)在 ModelC 不可能意外繼承不正確的 fly 實(shí)現(xiàn)代碼了,因?yàn)?Airplane 中的 pure virtual 函數(shù)迫使 ModelC 必須提供自己的 fly 版本:
class ModelC: public Airplane {
public:
virtual void fly(const Airport& destination);
...
};
void ModelC::fly(const Airport& destination)
{
//將C型飛機(jī)飛至指定的目的地
}
這個(gè)方案并非安全無(wú)虞,程序員還是可能因?yàn)榧糍N(copy-and-paste)代碼而招來(lái)麻煩,但它比原來(lái)的設(shè)計(jì)值得依賴。
Airplane::defaultFly 是一個(gè) non-virtual 函數(shù),這一點(diǎn)也很重要。因?yàn)闆](méi)有任何一個(gè) derived class 應(yīng)該重新定義此函數(shù)。
有些人反對(duì)以不同的函數(shù)分別提供接口和缺省實(shí)現(xiàn),向上述的 fly 和 defaultFly 那樣。我們可以利用 “pure virtual 函數(shù)必須在 derived class 中重新聲明,但它們可以擁有自己的實(shí)現(xiàn)” 這一事實(shí)。下面是 Airplane 繼承體系如何給 pure virtual 函數(shù)一份定義:
class Airplane {
public:
virtual void fly(const Airport& destination) = 0;
...
};
void Airplane::fly(const Airport& destination) // pure virtual 函數(shù)實(shí)現(xiàn)
{
// 缺省行為,將飛機(jī)飛至指定目的地
}
class ModelA: public Airplane {
public:
virtual void fly(const Airport& destination)
{
Airplane::fly(destination);
}
...
};
class ModelB: public Airplane {
public:
virtual void fly(const Airport& destination)
{
Airplane::fly(destination);
}
...
};
class ModelC: public Airplane {
public:
virtual void fly(const Airport& destination)
...
};
void ModelC::fly(const Airport& destination)
{
// 將 C 型飛機(jī)飛至指定目的地
}
身為 class 設(shè)計(jì)者,有時(shí)候你希望 derived class 只繼承成員函數(shù)的接口(也就是聲明):有時(shí)候你又希望 derived class 同時(shí)繼承函數(shù)的
接口和實(shí)現(xiàn),但又希望能夠覆寫(override)它們所繼承的實(shí)現(xiàn):又有時(shí)候你希望 derived class 同時(shí)繼承函數(shù)的接口與實(shí)現(xiàn),并且不允許覆寫任何東西。
讓我們考慮一個(gè)展現(xiàn)繪圖程序中各種幾何形狀的 class 繼承體系:
class Shape {
public:
virtual void draw() const = 0;
virtual void error(const std::string& msg);
int objectID() const;
};
class Rectangle : public Shape {...};
class Ellipse : public Shape {...};
Shape 的 pure virtual 函數(shù) draw 使它成為一個(gè)抽象 class。Shape 強(qiáng)烈影響了所有以 public 形式繼承它的 derived classes。因?yàn)椋撼蓡T函數(shù)的接口總是會(huì)被繼承。
首先考慮 pure virtual 函數(shù) draw:pure virtual 函數(shù)有兩個(gè)最突出的特性:它們必須被任何 “繼承了它們” 的具象 class 重新聲明,而且它們?cè)诔橄?class 中通常沒(méi)有定義。所以:聲明一個(gè) pure virtual 函數(shù)的目的是為了讓 derived class 只繼承函數(shù)接口。
Shape::draw 的聲明式乃是對(duì)具象 derived class 設(shè)計(jì)者說(shuō) “你必須提供一個(gè) draw 函數(shù),但我不干涉你怎么實(shí)現(xiàn)它”
令人意外的是,我們竟然可以為 pure virtual 函數(shù)提供定義。但調(diào)用它的唯一途徑是 “調(diào)用時(shí)明確指出其 class 名稱”:
Shape* ps = new Shape; //wrong! abstract class Shape
Shape* ps1 = new Rectangle;
ps1->draw(); //Rectangle::draw()
Shape* ps2 = new Ellipse;
ps2->draw(); //Ellipse::draw()
ps1->Shape::draw();
ps2->Shape::draw();
它可以提供一種機(jī)制,為簡(jiǎn)樸 impure virtual 函數(shù)提更平常更安全的缺省實(shí)現(xiàn)。
impure virtual 函數(shù)會(huì)提供一份實(shí)現(xiàn)代碼,derived classes 可能覆寫(override)它:
生命簡(jiǎn)樸的(非純)impure virtual 函數(shù)的目的,是讓 derived class 繼承該函數(shù)的接口和缺省實(shí)現(xiàn)。
考慮 Shape::error 這個(gè)例子。
其接口表示,每個(gè) class 都必須支持一個(gè) “當(dāng)遇上錯(cuò)誤時(shí)可調(diào)用” 的函數(shù),但每個(gè) class 可自由處理錯(cuò)誤。如果某個(gè) class 不想針對(duì)錯(cuò)誤作出任何特殊行為,可以退回到 Shape class 提供的缺省錯(cuò)誤處理行為。
但是允許 impure virtual 函數(shù)同時(shí)指定函數(shù)聲明和函數(shù)缺省行為,卻有可能造成危險(xiǎn)??紤] xyz 航空公司設(shè)計(jì)的繼承體系。該公司只有 A 和 B 兩種飛機(jī),相同方式飛行:
class Airport {...};
class Airplane {
public:
virtual void fly(const Airport& destination);
...
};
void Airplane::fly(const Airport& destination)
{
//缺省代碼,將飛機(jī)飛至指定目的地
}
class ModelA: public Airplane {...};
class ModelB: public Airplane {...};
為表示所有飛機(jī)都一定能飛,并闡明 “不同型飛機(jī)原則上需要不同的 fly 實(shí)現(xiàn)” Airplane::fly 被聲明為 virtual。然而為了避免在 ModelA 和 ModelB 中撰寫相同代碼,缺省飛行行為有 Airplane::fly 提供,它同時(shí)被 ModelA 和 ModelB 繼承。
現(xiàn)在,xyz 決定購(gòu)買一種新式 C 型飛機(jī),這種飛機(jī)的飛行方式不同。
xyz 的程序員在繼承體系中對(duì) C 型飛機(jī)添加了一個(gè) class,但由于他們急著讓新飛機(jī)上線服務(wù),忘了重新定義器 fly 函數(shù):
class ModelC: public Airplane {...};
然后在代碼中有一些諸如此類的動(dòng)作:
Airport PDX(...);
Airplane* pa = new ModelC;
...
pa->fly(PDX);
這將釀成大災(zāi)難:這個(gè)程序試圖用 ModelA 和 ModelB 的飛行方式來(lái)飛 ModelC。
問(wèn)題不在 Airplane::fly() 有缺省行為,而在于 ModelC 在未明白說(shuō)出 “我要” 的情況下就繼承了該缺省行為。我們可以做到 “提供缺省實(shí)現(xiàn)給 derived classes,但除非它們明確要求,否則免談”。此間伎倆在于切斷 “virtual 函數(shù)接口” 和其 “缺省實(shí)現(xiàn)” 之間的連接。下面是一種做法:
class Airplane {
public:
virtual void fly(const Airport& destination) = 0;
...
protected:
void defaultFly(const Airport& destination);
};
void Airplane::defaultFly(const Airport& destination)
{
// 缺省行為,將飛機(jī)飛至目的地
}
fly 已被改成為一個(gè) pure virtual 函數(shù),只提供飛行接口。缺省行為以 defaultFly 出現(xiàn)在 Airplane class 中。若想使用缺省實(shí)現(xiàn)(例如 ModelA 和 ModelB),可以在 fly 中對(duì) defaultFly 做一個(gè) inline 調(diào)用:
class ModelA: public Airplane {
public:
virtual void fly(const Airport& destination)
{
defaultFly(destination);
}
...
};
class ModelB: public Airplane {
public:
virtual void fly(const Airport& destination)
{
defaultFly(destination);
}
...
};
現(xiàn)在 ModelC 不可能意外繼承不正確的 fly 實(shí)現(xiàn)代碼了,因?yàn)?Airplane 中的 pure virtual 函數(shù)迫使 ModelC 必須提供自己的 fly 版本:
class ModelC: public Airplane {
public:
virtual void fly(const Airport& destination);
...
};
void ModelC::fly(const Airport& destination)
{
// 將 C 型飛機(jī)飛至指定的目的地
}
這個(gè)方案并非安全無(wú)虞,程序員還是可能因?yàn)榧糍N(copy-and-paste)代碼而招來(lái)麻煩,但它比原來(lái)的設(shè)計(jì)值得依賴。
Airplane::defaultFly 是一個(gè) non-virtual 函數(shù),這一點(diǎn)也很重要。因?yàn)闆](méi)有任何一個(gè) derived class 應(yīng)該重新定義此函數(shù)。
有些人反對(duì)以不同的函數(shù)分別提供接口和缺省實(shí)現(xiàn),向上述的 fly 和 defaultFly 那樣。我們可以利用 “pure virtual 函數(shù)必須在 derived class 中重新聲明,但它們可以擁有自己的實(shí)現(xiàn)” 這一事實(shí)。下面是 Airplane 繼承體系如何給 pure virtual 函數(shù)一份定義:
class Airplane {
public:
virtual void fly(const Airport& destination) = 0;
...
};
void Airplane::fly(const Airport& destination) // pure virtual 函數(shù)實(shí)現(xiàn)
{
//缺省行為,將飛機(jī)飛至指定目的地
}
class ModelA: public Airplane {
public:
virtual void fly(const Airport& destination)
{
Airplane::fly(destination);
}
...
};
class ModelB: public Airplane {
public:
virtual void fly(const Airport& destination)
{
Airplane::fly(destination);
}
...
};
class ModelC: public Airplane {
public:
virtual void fly(const Airport& destination)
...
};
void ModelC::fly(const Airport& destination)
{
// 將C型飛機(jī)飛至指定目的地
}