條款 34:區(qū)分接口繼承和實(shí)現(xiàn)繼承(一)

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ī)飛至指定目的地 
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容