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,使用某個Function Compute健康指數
EvilBadGuy ebg1(calchealth);
//人物2,使用某個函數對象計算健康指數
EyeCandyCharacter ecc1(healthcalculator());
//人物3,使用某個成員Function Compute健康指數
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到Null 物件中。
在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”的兩項組合。