(Effective C++)第六章 繼承與面向對象(Inheritance and Object-Oriented Design)

Public繼承意味着“is-a”,virtual函數意味着“必須被繼承”,non-virtual意味着“接口和實現都必須被繼承”。

8.1 條款32:確定你的public繼承塑模出is-a關係 (Make sure public inheritance models “is-a”)

C++最重要的一個規則是:public inheritance(公開繼承)意味“is-a”(是一種)的關係。如果你令class D(“Derived”)以public方式繼承class B(“Base”),你便是告訴C++編譯器說,每個類型爲D的對象同時也是一個類型爲B的對象,反之不成立。適用於base classes身上的每一件事情一定也適用於derived class身上。
但是有這樣的一個例子,企鵝(penguin)是一種鳥,鳥可以飛,但是企鵝不能飛。我們以繼承關係,它塑模出較佳的真實性,如下:
class Bird {
。。。   //沒有聲明fly函數
};
class FlyingBird:public Bird{
public:
virtual void fly();

};      
class Penguin : public Bird{ //沒有聲明fly函數

};
示例8-1-1  企鵝不會飛
此刻,企鵝是鳥,但是不能飛。
世界上並不存在一個“適用於所有軟件”的完美設計。所謂最佳設計,取決於系統希望做什麼事,包括現在和未來。

class Bird {//聲明fly函數
public:
virtual void fly();
};
class Penguin : public Bird{
public:
virtual void fly() { error();}
};
示例8-1-2  企鵝嘗試飛,是一種錯誤
另有一種實現派別是,企鵝可以嘗試飛,但是那麼做是一種錯誤。“企鵝不會飛”這個限制可由編譯期間強制實施,但是“企鵝嘗試飛行,是一種錯誤”這條規則,只有運行期間才能檢測出來。
條款18說過,好的藉口可以防止無效的代碼通過編譯,因此我們更偏重於“企鵝不會飛”的設計。
is-a並非是唯一存在於class之間的關係。另外兩個常見的關係是has-a(有一個)和is-implementation-in-terms-of(根據某物實現出)。分別在條款38和39討論。

8.2 條款33:避免遮掩繼承而來的名稱 (Avoid hiding inherited names)

C++的名稱遮掩規則所作的唯一事情,就是遮掩名稱,不管名稱是否有相同的類型,用內層作用域的名稱遮掩外層作用域的名稱。Derived classes的名稱會遮掩base classes的名稱。
成員函數被重載的特徵:
(1)相同的範圍(在同一個類中);
(2)函數名字相同;
(3)參數不同;
(4)virtual關鍵字可有可無。
(5)返回值可以不同。
    覆蓋是指派生類函數覆蓋基類函數,特徵是:
(1)不同的範圍(分別位於派生類與基類);
(2)函數名字相同;
(3)參數相同;
(4)基類函數必須有virtual關鍵字。
“隱藏”是指派生類的函數屏蔽了與其同名的基類函數,規則如下:
(1)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。
(2)如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。

下例是derived class作用域被嵌套在base class作用域內,如
class Base {
public:
virtual void mf1()=0;
virtual void mf2();
void mf3();

private:
int x;
};
class Derived:public Base{
public:
virtual void mf1();
void mf4();

};      
void Derived:: mf4{
     mf2();
};
示例8-2-1  名稱被遮掩-
編譯器的做法是查找local作用域(也就是mf4覆蓋的作用域),是否有mf2;查找其外圍作用域,也就是class Derived覆蓋的作用域,繼續往外圍移動,本例是base class,查找內含Base class的那個namespaces的作用域,最後查global作用域。

class Base {
public:
virtual void mf1()=0;
virtual void mf1(int);
virtual void mf2();
void mf3();
virtual void mf3(double);

private:
int x;
};
class Derived:public Base{
public:
virtual void mf1();
void mf3();
void mf4();

};      
void Derived:: mf4{
     mf2();
};

//使用
Derived d;
int x;
d.mf1();  //ok,調用Derived::mf1
d.mf1(x); //error,因爲Derived::mf1遮掩了Base::mf1
d.mf2();  //ok,調用Base::mf2
d.mf3();  //ok,調用Derived::mf3
d.mf3(x); //error,因爲Derived::mf3遮掩了Base::mf3
示例8-2-2  名稱被遮掩二
解決之道之一,使用using 聲明式:
class Derived:public Base{
public:
using Base::mf1; //讓Base class內名爲mf1和mf3的所有東西
using Base::mf3; //在Derived class作用域可見
virtual void mf1();
void mf3();
void mf4();

};      

//使用
Derived d;
int x;
d.mf1();  //ok,調用Derived::mf1
d.mf1(x); //ok,因爲調用Base::mf1
d.mf2();  //ok,調用Base::mf2
d.mf3();  //ok,調用Derived::mf3
d.mf3(x); //ok,因爲調用Base::mf3
示例8-2-3  名稱被遮掩三
解決之道之二,使用轉交函數(forwarding function):
class Derived:private Base{
public:
virtual void mf1(){ //轉交函數,暗自成爲inline,見條款30
Base::mf1();
};

};      

//使用
Derived d;
int x;
d.mf1();  //ok,調用Derived::mf1
d.mf1(x); //error, 因爲Derived::mf1遮掩了Base::mf1
示例8-2-4  名稱被遮掩四
Inline轉交函數的另一個用途是爲那些不支持using聲明式的老舊編譯器另闢一條新路,將繼承而得的名稱匯入derived class作用域內。

8.3 條款34:區分藉口繼承和實現繼承 (Differentiate between inheritance of interface and inheritance of implementation)

Public繼承是由兩部分組成:函數接口(function interfaces)繼承和函數實現(function implementation)繼承。
身爲class設計者,有時候希望derived class只繼承成員函數接口;有時候希望同時繼承函數接口和實現;有時候希望覆寫他們所繼承的實現;有時候希望derived classes同時繼承函數的接口和實現,但是不允許覆寫任何東西。

class Shape {
public:
virtual void draw() const = 0;
virtual void error(const std::string &msg);
void objectID() const;

};
class Rectangle:public Shape {…};
class Ellipse:public Shape {…};

//使用
Shape *ps = new Shape; //error,Shape是抽象的
Shape *ps1 = new Rectangle;
ps1->draw();
shape ps2 = new Ellipse;
ps2->draw();
ps1->Shape::draw();
ps2->Shape::draw();
示例8-3-1  名稱被遮掩四
?    成員函數的接口總是會被繼承。
Shape class聲明瞭三個函數,都被繼承下來。

?    聲明一個pure virtual函數的目的是爲了讓derived classes只繼承函數接口。
你必須提供一個draw哈思楠,但是不干涉你怎麼實現它。

?    聲明簡樸的非純虛(impure virtual)函數的目的,是讓derived classes繼承該函數的接口和缺省實現。
避免將所有成員函數聲明爲virtual。某些函數就是不該在derived class中被重新定義。

?    聲明non-virtual函數的目的是爲了令derived classes繼承函數的接口及一份強制性實現。
每個Shape對象都有一個用來產生對象識別碼的函數,此識別碼總是採用相同的計算方法,該方法用Shape::objected的定義式決定。
由於non-virtual函數代表的意義是不變性(invariant)凌駕特異性(specialization),所以它絕不會在derived class中被重新定義的。
避免將所有函數聲明爲non-virtual,否則,會使得derived classes沒有餘裕空間進行特化工作。

8.4 條款35:考慮virtual函數以外的其他選擇 (consider alternatives to virtual functions)

假設你正在寫一個視頻遊戲軟件,你打算爲遊戲內的人物設計一個繼承體系。你因此決定提供一個成員函數healthValue,它會返回一個整數,表示人物的健康程度。於是,將healthValue聲明爲virtual是再明白不過的做法(條款34):
class GameCharacter {
public:
virtual int healthValue() const; //返回人物的健康指數,
//derived classes可重新定義它
};
示例8-4-1  healthValue的虛函數聲明
?    藉由Non-Virtual Interface手法實現Template Method模式。
該流派的擁護建議是,較好的設計是保留healthvalue爲public成員函數,但是讓它成爲non-virtual,並調用一個private函數進行實際的工作。

class GameCharacter {
public:
virtual int healthValue() const //derived class不重新定義它,條款36
{
    。。。//做一些事前工作,鎖定互斥器,日誌記錄,驗證class約束條件
    int ret = doHealthVaule();  //做真正的工作
    …  ///做一些事後工作,解除鎖定,再次驗證class約束條件
    return ret;
}
private:
virtual int doHealthValue const //derived classes可重新定義它
{
…      //缺省算法,計算健康指數
}
};
示例8-4-2  healthValue的NVI手法
這一基本設計,也就是“令客戶通過public non-virtual成員函數間接調用private virtual函數”,稱爲non-virtual interface(NVI)手法。它是所謂Template Method設計模式(與C++templates並無關聯)的一個獨特表示形式。我把這個non-virtual函數(healthValue)稱爲virtual函數的外覆器(wrapper)。
在NVI手法下其實沒有必要讓virtual函數一定得是private。“重新定義virtual函數”表示某些事“如何被完成”,“調用virtual函數“則表示它”何時“被完成。這些事情都是獨立不相干的。

?    藉由Function Pointers實現Strategy模式。
另一個更戲劇性的設計主張是“人物健康指數的計算與人物類型無關“。例如,我們可能要求每個人物的構造函數接收一個指針,指向一個健康計算函數,而我們可以調用該函數進行實際計算。

class GameCharacter;   //前置聲明
//計算健康指數的缺省算法
int defaultHealthCalc(const GameCharacter &gc);
class GameCharacter {
public:
typedef int (*HealthCalcFunc)(const GameCharacter &);
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
:healthFunc(hcf)
{}
int healthVaule() const
{ reurn healthFunc(*this);}
private:
HealthCalcFunc healthFunc;
};
示例8-4-3 healthValue的Function Pointers手法
這個做法是常見的Strategy設計模式的簡單應用。它還提供了某些有趣彈性(優點):
1)同一人物類型之不同實體可以有不同的健康計算函數。例如
2)某已知人物之健康指數計算函數可在運行期變更。例如GameCharacter可提供一個成員函數setHealthCalculator,用來替換當前的健康指數計算函數。

這種設計,如果需要non-public信息進行精確計算,就需要public member成員函數提供相應的訪問權限。實際上任何時候當你將class內的某個機能(也許取道某個成員函數)替換爲class外部的某個等價機能(也許取道某個non-member,non-friend函數或另個class的non-friend成員函數),這都是潛在爭議點。
一般而言,唯一能夠解決“需要以non-member函數訪問class的non-public成分“的辦法就是:弱化class的封裝。例如,class可聲明那個non-member函數爲friends。

?    藉由trl::function實現Strategy模式。
如果我們不再使用函數指針,而是改用一個類型爲trl::function的對象。如下

class GameCharacter;   //前置聲明
//計算健康指數的缺省算法
int defaultHealthCalc(const GameCharacter &gc);
class GameCharacter {
public:
// HealthCalcFunc可以是任何可調用物,可被調用並接受任何兼容於
//GameCharacter植物,返回任何兼容於int的東西
typedef std::trl::function <int (const GameCharacter &)> HealthCalcFunc;
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
:healthFunc(hcf)
{}
int healthVaule() const
{ reurn healthFunc(*this);}
private:
HealthCalcFunc healthFunc;
};
示例8-4-4 healthValue的Function Pointers手法
如你所見,HealthCalcFunc是個typedef,用來表現trl::function的某個具現體,意味該具現體的行爲像一般的函數指針。<int (const GameCharacter &)>的紅色部分是具現體(instantiation)的目標籤名式(target signature)。這個trl::function所產生的對象可以持有任何與此簽名時兼容的可調用物(callable entity)。所謂兼容,意思就是這個可調用物的參數可被隱式轉換爲const GameCharacter &,而其返回類型可被隱式轉爲int。
和前一個設計相比,這個設計幾乎相同,唯一不同的是GameCharacter持有一個trl::function對象,相當於一個指向函數的泛化指針,更具有彈性。
short calchealth(const GameCharater &); //健康計算函數,返回類型爲non-int
struct healthcalculator{  //健康計算函數對象
  int operator()(const GameCharater &) const
{…}
};
class GameLevel{
public:
//成員函數,計算健康,返回類型爲non-int
float health (const GameCharacter &gc) const;
};
class EvilBadGuy:public GameCharacter{  //同前
。。。
};
//另一個人物類型,假設構造函數與EvilBadGuy同
class EyeCandyCharacter:public GameCharacter{  
。。。
};
//人物1,使用某個函數計算健康指數
EvilBadGuy ebg1(calchealth);
//人物2,使用某個函數對象計算健康指數
EyeCandyCharacter ecc1(healthcalculator());
//人物3,使用某個成員函數計算健康指數
GameLevel currentLevel;
EvilBadGuy ebg2(std::trl::bind(&GameLevel::health, currentLevle, _1) ));
示例8-4-5 healthValue的Function Pointers手法的應用彈性
“_1”意味“當ebg2調用GameLevel::health時系以currentLevel作爲GameLevel對象“。

?    古典的Strategy模式。
典型的Strategy會將健康計算做成一個分離的繼承體系中的virtual成員函數。
 
圖例8-4-1 類設計圖
如果你未精通UML符號,這圖的意思是:GameCharater是某個繼承體系的根類,體系中EvilBadGuy 和EyeCandyCharacter都是derived classes;HealthCalcFunc是另一個繼承體系的根類,體系中SlowHealthLoser和FastHealthLoser都是derived classes。每一個GameCharater對象都內含一個指針,指向來自HealthCalcFunc繼承體系的對象。
class GameCharacter;   //前置聲明
class  HealthCalcFunc{  
public:
  virtual int calc(const GameCharater & gc) const
{…}
};
HealthCalcFunc defaultHealthCalc;

class GameCharacter {
public:
explicit GameCharacter(HealthCalcFunc phcf = &defaultHealthCalc)
:pHealthFunc(phcf)
{}
int healthVaule() const
{ reurn pHealthFunc->calc()(*this);}
private:
HealthCalcFunc *pHealthFunc;
};
示例8-4-6 healthValue的Function Pointers手法的應用彈性
這個解法的吸引力在於,熟悉標準Strategy模式的人很容易辨認它,而它還提供“將一個既有的健康算法納入使用“的可能性——只要爲HealthCalcFunc繼承體系添加一個derived classes即可。


8.5 條款36:絕不重新定義繼承而來的non-virtual函數 (Never redefine a function 's inherited non-virtual function)

假設class D系由class B以public形式派生而來。
class Base {
public:
void mf();

};
class Derived:public Base{
public:
void mf(); //遮掩了B::mf,條款33

};      
D x;
B* pB = &x;
D* pD = &x;
pB->mf(); //調用B::mf
pD->mf(); //調用D::mf
示例8-5-1  名稱遮掩
造成次一兩面行爲的原因是,non-virtual函數如B::mf和D::mf都是靜態綁定(statically bound,條款37)。而virtual函數卻是動態綁定(dynamicallly bound,條款37)。如果mf是個virtual函數,不論通過pB或pD調用mf,都會導致調用D::mf,因爲pB和pD真正指的都是類型爲D的對象。
所以,絕不重新定義繼承而來的non-virtual函數。如果mf是B的一個non-virtual函數,B的derived classes一定會繼承mf的接口和實現。

8.6 條款37:絕不重新定義繼承而來的缺省參數值(Never redefine a function 's inherited default parameter value)

對象的所謂靜態類型(static type),就是它在程序中被聲明時所採用的類型。
class Shape {
public:
enum ShapeColor{Red, Green, Blue} ;
virtual void draw(ShapeColor color = Red) const = 0;

};
class Rectangle:public Shape {
public:
virtual void draw(ShapeColor color = Green) const; //糟糕

};      
class Circle:public Shape {
public:
virtual void draw(ShapeColor color) const ;
//請注意:當客戶以對象調用此函數,一定要指定參數值,
//因爲靜態綁定下這個函數並不從其base繼承缺省參數
//但是若以指針或reference調用次函數,可以不知道參數值
//因爲動態綁定下這個函數會從其base繼承缺省參數

};     
Shape *ps;    //靜態類型爲shape*
Shape *pc = new Circle; //靜態類型爲shape*
Shape *pr = new Rectangle; //靜態類型爲shape*

D* pD = &x;
pB->mf(); //調用B::mf
pD->mf(); //調用D::mf
示例8-6-1  靜態類型與動態類型
本例中,ps,pc和pr都被聲明爲pointer-to-shape類型,所以他們都以它爲靜態類型,不論它們真正指向什麼,他們的靜態類型都是shape*。
對象的所謂動態類型(dynamic type)則是指“目前所指對象的類型“。上例中,pc的動態類型是Circle*,pr的動態類型是Rectangle*,而ps沒有動態類型,因爲它尚未指向任何對象。
動態類型可以在程序執行過程中改變(通常是經由賦值動作)。
ps = pc;   //ps的動態類型是Circle*
ps = pr;   // ps的動態類型是Rectangle*
virtual函數系動態綁定而來,意思調用一個virtual函數時,究竟調用哪份函數實現代碼,取決於發出調用的那個對象的動態類型。
pc->draw(Shape::Red);   //調用Circle::draw(Shape::Red)
pr->draw(Shape::Red);   //調用Rectangle::draw(Shape::Red)
Virtual函數時動態綁定,而缺省參數卻是靜態綁定。 意思是調用一個定義域derived class內的virtual函數的同時,卻使用了base class爲它所指定的缺省參數值:
pr->draw();          //調用Rectangle::draw(Shape::Red),
//本意是Rectangle::draw(Shape:: Green)
以上事實不只侷限於ps,pc和pr是指針情況,也符合reference的情況。

8.7 條款38:通過複合塑模出has-a或“根據某實物實現出” (Mode "has-a" or  " is-implementation-in-terms-of" through composition)

複合(composition)是類型之間的一種關係,當某種類型的對象內含它種關係類型的對象,便是這種關係。
程序中的對象其實相當於你所塑造的世界中的某些事物,例如人,氣場,一張張視頻畫面等,這樣的對象屬於應用域(application domain)。其他對象則純粹是實現細節上的人工製品,像是緩衝區,互斥器和查找樹等等,這些對象相當於軟件的實現域(implementation domain)。在應用域,複合意味has-a(有一個)。在實現域,複合意味is-implementation-in-terms-of(根據某物實現出)。
假設你需要一個template,希望做一組classes用來表示由不重複對象組成的sets。直覺是採用STL的set  template。但是,set的實現往往招致每個元素好用三個指針,因爲sets以平衡查找樹實現而成,使他們在查找、安插、移除元素時保證擁有對數時間效率。但是空間有限,只好自己設計一個set template,底層採用linked lists。而此時就是複用std::list。也就是set<T>繼承list<T>。但是list可以含有重複元素,而set不能含有重複元素。因此,“set是一種list“並不是真。也就不能用public繼承塑模它們。而正確的做法是,set對象可 根據一個list對象實現出來。這就是is-implementation-in-terms-of。

8.8 條款39:明智而審慎地使用private繼承(Use private inheritance judiciously)

大多數繼承相當於is-a,這是指public繼承,不是private繼承。複合和private繼承都意味着is-implementation-in-terms-of,但是複合交易理解,所以無論什麼時候,只要可以,你還是應該選擇複合。當你面對“並不存在is-a關係“的兩個classes,其中一個需要訪問另一個的protected成員,或需要重新定義一個或多個virtual函數,private繼承就派上用場。還有一種激進情況,當你所處理的class不帶任何數據,也可以使用private繼承。
第一:如果classes之間的繼承關係是private,編譯器不會自動將一個derived class對象(例如Student)轉換爲一個base class對象(例如Person)。第二:由private base class繼承而來的所有成員,在derived class中都會變成private屬性,縱使它們在base class中原本是protected或public屬性。
Private繼承純粹只是一種實現技術,在軟件“設計“層面上沒有意義,其意義只及於軟件實現層面。
假設我們的程序涉及Widgets,還需要一個統計功能。爲了讓Widget重新定義Timer內的虛函數,Widget必須繼承自Timer,但是public在此例不適當,因爲Widget並不是一個Timer,如下:
class Timer{
public:
explicit Timer(int tickFrequency);
virtual void onTick() const; //定時器每滴答一次,此函數自動被調用一次

};  
class Widget:private Timer{
private:
virtual void onTick() const; //藉由private繼承,Timer的public屬性
//變成private,而我們放在private內,避免誤導客戶

};    
示例8-8-1  private繼承
如果我們覺得以複合取而代之,也是可以的,如下:
class WidgetTimer:public Timer{
public:
virtual void onTick() const;

};  
class Widget{
private:
WidgetTimer timer;

};    
示例8-8-2  複合
我們之所以選在複合,原因如下
?    你或許會想設計Widget使它得以擁有derived classes,但是同時你可能會想阻止derived classes 重新定義onTick。
如果Widget繼承自Timer,上面的想法就不可能實現,即使是private繼承也不行。
?    你或許會想要將Widget的編譯依存性降至最低。
如果Widget繼承Timer,當Widget被編譯時Timer的定義必須可見。
但是你所處理的class不帶任何數據,偏向使用private繼承。

class Empty {};    //沒有數據
//應該只需要一個int空間
class HoldsAnInt:private Empty{  
private:       
int x;

};      class Empty {};    //沒有數據
class HoldsAnInt{  //應該只需要
private:        //一個int空間
int x;
Empty e;

};  
示例8-8-3(a) private繼承           示例8-8-3(b))組合
在8-8-3(b)中,sizeof(HoldsAnInt) > sizeof(int),原因是大多數編譯器sizeof(Empty)獲得1,面對“大小爲零之獨立(非附屬)對象“,C++官方勒令默默安插一個char到空對象中。
在8-8-3(a)中,sizeof(HoldsAnInt) =sizeof(int),這是所謂的EBO(empty base optimization,空白基類最優化)。EBO一般只在單一繼承下才可行。
但是,現實的“empty“ classes並不是真的是Empty。雖然它們從未擁有non-static成員變量,卻往往內含typedef,enums,static成員變量,或non-virtual函數。
這是與複合的不同之處,private繼承可以造成empty classes base最優化。這對於”對象尺寸最小化“的程序開發者而言,可能很重要。

8.9 條款40:明智而審慎地使用多重繼承 (Use multiple inheritance judiciously)

多重繼承需要清楚的一件事,當MI(multiple inheritance)進入設計景框,程序有可能從一個以上的base classes繼承相同的名稱(如函數,typedef等)。它可能導致新的歧義性,以及對virtual繼承的需要。比如,鑽石型多重繼承。
 
圖例8-9-1  鑽石型多重繼承
假設File類有成員變量filename,那IOFile該有多個份這個名稱的數據呢?從繼承來說,IOFile對象只該有一個文件名稱。爲了這樣做,你必須令所有直接繼承base class的derived class採用“virtual繼承”。

class File{};
class InputFile: virtual public File{};
class OutputFile: virtual public File{};
class IOFile: virtual public InputFile, virtual public OutputFile {};  
示例8-9-1  virtual繼承
爲了避免繼承得來的成員變量重複,編譯器必須提供諾幹戲法,而其後果是:使用virtual繼承的那些classes所產生的對象往往比使用non-virtual繼承的兄弟們體積大,訪問virtual base classes的成員變量時,也比訪問non-virtual base classes的成員變量速度慢。Virtual base的初始化責任是由繼承體系的最底層class負責。
所以,我們的忠告是:第一,非必要不要使用virtual base。第二,如果必須使用virtual base classes,儘可能避免在其中放置數據。

class IPerson {
public:
virtual ~IPerson();
virtual std:string name() const = 0;
virtual std:string birthDate() const = 0;
};
class DatabaseID{…};
class PersonInfo {
public:
explicit PersonInfo(DatabaseID pid);
virtual ~PersonInfo();
virtual const char* theName() const;
virtual const char* theBirthDate() const;
virtual const char* valueDelimOpen() const;
virtual const char* valueDelimClose() const;

};
const char* PersonInfo::valueDelimOpen() const
{
    return "[";
}
const char* PersonInfo::valueDelimClose() const
{
    return "]";
}

const char* PersonInfo::theName() const
{
   static char value[NAME_LEN_MAX];
std::strcpy(value, valueDelimOpen());
… //將value的字符串添加到這個對象的name成員變量中
std::strcat(value, valueDelimClose());
return value;
}
class CPerson:public IPerson,private PersonInfo{
public:
explicit CPerson(DatabaseID pid):PersonInfo(pid){}
//實現必須的IPerson的成員函數
virtual std::string name const
{ return PersonInfo::theName();}
virtual std::string birthDate const
{ return PersonInfo::theBirthDate();}
private:
//重新定義繼承而來的virtual函數
const char* valueDelimOpen() const {return  "";}
const char* valueDelimClose() const{ return  "";}
};

示例8-9-2  多重繼承
IPerson類是抽象類,只提供接口。早有PersonInfo類的存在,可以爲CPerson類提供所需的實質東西。CPerson和PersonInfo的關係是is-implementation-in-terms-of。而實現這種關係有兩種技術:複合和private繼承。複合雖然比較受歡迎,但是如果需要重新定義virtual函數,那麼繼承是必要的。本例需要重新定義valueDelimOpen和valueDelimClose,所以單純的複合無法應對。但是CPerson也必須實現Iperson接口,那需得以public繼承才能完成。
所以,多重繼承的正當用途之一是,涉及“public繼承某個Interface class”和“private繼承某個協助實現的class”的兩項組合。


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