亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

Effective C++ 2e Item43

2019-09-10 09:07:14
字體:
來源:轉載
供稿:網友

條款43: 明智地使用多繼承

要看是誰來說,多繼承(MI)要么被認為是神來之筆,要么被當成是魔鬼的造物。支持者宣揚說,它是對真實世界問題進行自然模型化所必需的;而批評者爭論說,它太慢,難以實現,功能卻不比單繼承強大。更讓人為難的是,面向對象編程語言領域在這個問題上至今仍存在分歧:C++,Eiffel和the Common LISP Object System (CLOS)提供了MI;Smalltalk,Objective C和Object Pascal沒有提供;而Java只是提供有限的支持。可憐的程序員該相信誰呢?

在相信任何事情之前,首先得弄清事實。C++中,關于MI一條不容爭辯的事實是,MI的出現就象打開了潘朵拉的盒子,帶來了單繼承中絕對不會存在的復雜性。其中,最基本的一條是二義性(參見條款26)。如果一個派生類從多個基類繼承了一個成員名,所有對這個名字的訪問都是二義的;你必須明確地說出你所指的是哪個成員。下面的例子取自ARM(參見條款50)中的一個專題討論:

class Lottery {
public:
 virtual int draw();

 ...

};

class GraphicalObject {
public:
 virtual int draw();

 ...

};

class LotterySimulation: public Lottery,
/t/t/t public GraphicalObject {

 .../t/t/t  // 沒有聲明draw

};

LotterySimulation *pls = new LotterySimulation;

pls->draw();/t/t   // 錯誤! ---- 二義
pls->Lottery::draw();/t  // 正確
pls->GraphicalObject::draw();  // 正確

這段代碼看起來很笨拙,但起碼可以工作。遺憾的是,想避免這種笨拙很難。即使其中一個被繼承的draw函數是私有成員從而不能被訪問,二義還是存在。(對此有一個很好的理由來解釋,但完整的說明在條款26中提供,所以此處不再重復。)

顯式地限制修飾成員不僅很笨拙,而且還帶來限制。當顯式地用一個類名來限制修飾一個虛函數時,函數的行為將不再具有虛擬的特征。相反,被調用的函數只能是你所指定的那個,即使調用是作用在派生類的對象上:

class SpecialLotterySimulation: public LotterySimulation {
public:
 virtual int draw();

 ...

};

pls = new SpecialLotterySimulation;

pls->draw();/t/t        // 錯誤! ---- 還是有二義
pls->Lottery::draw();/t       // 調用Lottery::draw
pls->GraphicalObject::draw();       // 調用GraphicalObject::draw

注意,在這種情況下,即使pls指向的是SpecialLotterySimulation對象,也無法(沒有 "向下轉換" ---- 參見條款39)調用這個類中定義的draw函數。

沒完,還有呢。Lottery和GraphicalObject中的draw函數都被聲明為虛函數,所以子類可以重新定義它們(見條款36),但如果LotterySimulation想對二者都重新定義那該怎么辦?令人沮喪的是,這不可能,因為一個類只允許有唯一一個沒有參數、名稱為draw的函數。(這個規則有個例外,即一個函數為const而另一個不是的時候 ---- 見條款21)

從某一方面來說,這個問題很嚴重,嚴重到足以成為修改C++語言的理由。ARM中就討論了一種可能,即,允許被繼承的虛函數可以 "改名" ;但后來又發現,可以通過增加一對新類來巧妙地避開這個問題:

class AuxLottery: public Lottery {
public:
 virtual int lotteryDraw() = 0;

 virtual int draw() { return lotteryDraw(); }
};

class AuxGraphicalObject: public GraphicalObject {
public:
 virtual int graphicalObjectDraw() = 0;

 virtual int draw() { return graphicalObjectDraw(); }
};


class LotterySimulation: public AuxLottery,
/t/t/t public AuxGraphicalObject {
public:
 virtual int lotteryDraw();
 virtual int graphicalObjectDraw();

 ...

};

這兩個新類, AuxLottery和AuxGraphicalObject,本質上為各自繼承的draw函數聲明了新的名字。新名字以純虛函數的形式提供,本例中即lotteryDraw和graphicalObjectDraw;函數是純虛擬的,所以具體的子類必須重新定義它們。另外,每個類都重新定義了繼承而來的draw函數,讓它們調用新的純虛函數。最終效果是,在這個類體系結構中,有二義的單個名字draw被有效地分成了無二義但功能等價的兩個名字:lotteryDraw和graphicalObjectDraw:

LotterySimulation *pls = new LotterySimulation;

Lottery *pl = pls;
GraphicalObject *pgo = pls;

// 調用LotterySimulation::lotteryDraw
pl->draw();

// 調用LotterySimulation::graphicalObjectDraw
pgo->draw();

這是一個集純虛函數,簡單虛函數和內聯函數(參見條款33)綜合應用之大成的方法,值得牢記在心。首先,它解決了問題,這個問題說不定哪天你就會碰到。其次,它可以提醒你,使用多繼承會導致復雜性。是的,這個方法解決了問題,但僅僅為了重新定義一個虛函數而不得不去引入新的類,你真的愿意這樣做嗎?AuxLottery和AuxGraphicalObject類對于保證類層次結構的正確運轉是必需的,但它們既不對應于問題范疇(problem domain )的某個抽象,也不對應于實現范疇(implementation domain)的某個抽象。它們單純是作為一種實現設備而存在,再沒有別的用處。你一定知道,好的軟件是 "設備無關" 的,這條法則在此也適用。

將來使用MI還會面臨更多的問題,二義性問題(盡管有趣)只不過是剛開始。另一個問題基于這樣一個實踐經驗:一個起初象下面這樣的繼承層次結構:

class B { ... };
class C { ... };
class D: public B, public C { ... };

/t/t    B    C
/t/t         /
/t/t        /
/t/t       /
/t/t       D

往往最后悲慘地發展成象下面這樣:

class A { ... };
class B : virtual public A { ... };
class C : virtual public A { ... };
class D: public B, public C { ... };

/t/t       A  
/t/t       /
/t/t      /  
/t/t     /    
/t/t    B    C
/t/t         /
/t/t        /
/t/t       /
/t/t       D

鉆石可能是女孩最好的朋友,也許不是;但肯定的是,象這樣一種鉆石形狀的繼承結構絕對不可能成為我們的朋友。如果創建了象這樣的層次結構,就會立即面臨這樣一個問題:是不是該讓A成為虛基類呢?即,從A的繼承是否應該是虛擬的呢?現實中,答案幾乎總是 ---- 應該;只有極少數情況下會想讓類型D的對象包含A的數據成員的多個拷貝。正是認識到這一事實,上面的B和C將A聲明為虛基類。

遺憾的是,在定義B和C的時候,你可能不知道將來是否會有類去同時繼承它們,而且知不知道這一點實際上對正確地定義這兩個類沒有必要。對類的設計者來說,這實在是進退兩難。如果不將A聲明為B和C的虛基類,今后D的設計者就有可能需要修改B和C的定義,以便更有效地使用它們。通常,這很難做到,因為A,B和C的定義往往是只讀的。例如這樣的情況:A,B和C在一個庫中,而D由庫的用戶來寫。

另一方面,如果真的將A聲明為B和C的虛基類,往往會在空間和時間上強加給用戶額外的開銷。因為虛基類常常是通過對象指針來實現的,并非對象本身。自不必說,內存中對象的分布是和編譯器相關的,但一條不變的事實是:如果A作為 "非虛" 基類,類型D的對象在內存中的分布通常占用連續的內存單元;如果A作為 "虛" 基類,有時,類型D的對象在內存中的分布占用連續的內存單元,但其中兩個單元包含的是指針,指向包含虛基類數據成員的內存單元:

A是非虛基類時D對象通常的內存分布:

/t   A部分+ B部分+ A部分 + C部分 + D部分

A是虛基類時D對象在某些編譯器下的內存分布:

/t/t/t   ------------------------------------------------
/t/t/t   |/t/t/t/t/t/t   |
/t/t/t   |/t/t/t/t/t/t  +
/t  B部分 + 指針 + C部分 + 指針 + D部分 + A部分
/t/t/t/t/t/t      |/t/t       +
/t/t/t/t/t/t      |/t/t        |
/t/t/t/t/t/t      ------------------------

即使編譯器不采用這種特殊的實現策略,使用虛繼承通常也會帶來某種空間上的懲罰。

考慮到這些因素,看來,在進行高效的類設計時如果涉及到MI,作為庫的設計者就要具有超凡的遠見。然而現在的年代,常識都日益成為了稀有品,因而你會不明智地過多依賴于語言特性,這就不僅要求設計者能夠預計得到未來的需要,而且簡直就是要你做到徹底的先知先覺(參見條款M32)。

當然,這也可以說成是在虛函數和非虛函數間選擇,但還是有重大的不同。條款36說明,虛函數具有定義明確的高級含義,非虛函數也同樣具有定義明確的高級含義,而且它們的含義有顯著的不同,所以在清楚自己想對子類的設計者傳達什么含義的基礎上,在二者之間作出選擇是可能的。但是,決定基類是否應該是虛擬的,則缺乏定義明確的高級含義;相反,決定通常取決于整個繼承的層次結構,所以除非知道了整個層次結構,否則無法做出決定。如果正確地定義出個類之前需要清楚地知道將來怎么使用它,這種情況下將很難設計出高效的類。

就算避開了二義性問題,并且解決了是否應該從基類虛擬繼承的疑問,還是會有許多復雜性問題等著你。為了長話短說,在此我僅提出應該記住的其它兩點:

? 向虛基類傳遞構造函數參數。非虛繼承時,基類構造函數的參數是由緊臨的派生類的成員初始化列表指定的。因為單繼承的層次結構只需要非虛基類,繼承層次結構中參數的向上傳遞采用的是一種很自然的方式:第n層的類將參數傳給第n-1層的類。但是,虛基類的構造函數則不同,它的參數是由繼承結構中最底層派生類的成員初始化列表指定的。這就造成,負責初始化虛基類的那個類可能在繼承圖中和它相距很遠;如果有新類增加到繼承結構中,執行初始化的類還可能改變。(避免這個問題的一個好辦法是:消除對虛基類傳遞構造函數參數的需要。最簡單的做法是避免在這樣的類中放入數據成員。這本質上是Java的解決之道:Java中的虛基類(即,"接口")禁止包含數據)

? 虛函數的優先度。就在你自認為弄清了所有的二義之時,它們卻又在你面前搖身一變。再次看看關于類A,B,C和D的鉆石形狀的繼承圖。假設A定義了一個虛成員函數mf,C重定義了它;B和D則沒有重定義mf:

/t     A   virtual void mf();
/t     /
/t    /  
/t   /    
/t  B    C   virtual void mf();
/t       /
/t      /
/t     /
/t     D

根據以前的討論,你會認為下面有二義:

D *pd = new D;
pd->mf();/t/t      // A::mf或者C::mf?

該為D的對象調用哪個mf呢,是直接從C繼承的還是間接(通過B)從A繼承的那個呢?答案取決于B和C如何從A繼承。具體來說,如果A是B或C的非虛基類,調用具有二義性;但如果A是B和C的虛基類,就可以說C中mf的重定義優先度高于最初A中的定義,因而通過pd對mf的調用將(無二義地)解析為C::mf。如果你坐下來仔細想想,這正是你想要的行為;但需要坐下仔細想想才能弄懂,也確實是一種痛苦。

也許至此你會承認MI確實會導致復雜化。也許你認識到每個人其實都不想使用它。也許你準備建議國際C++標準委員會將多繼承從語言中去掉;或者至少你想向你的老板建議,全公司的程序員都禁止使用它。

也許你太性急了。

請記住,C++的設計者并沒有想讓多繼承難以使用;恰恰是,想讓一切都能以更合理的方式協調工作,這本身會帶來某些復雜性。上面的討論中你會注意到,這些復雜性很多是由于使用虛基類引起的。如果能避免使用虛基類 ---- 即,如果能避免產生那種致命的鉆石形狀繼承圖 ---- 事情就好處理多了。

例如,條款34中講到,協議類(Protocol class)的存在僅僅是為派生類制定接口;它沒有數據成員,沒有構造函數,有一個虛析構函數(參見條款14),有一組用來指定接口的純虛函數。一個Person協議類看起來象下面這樣:

class Person {
public:
 virtual ~Person();

 virtual string name() const = 0;
 virtual string birthDate() const = 0;
 virtual string address() const = 0;
 virtual string nationality() const = 0;
};

這個類的用戶在編程時必須使用Person的指針或引用,因為抽象類不能被實例化。

為了創建 "可以作為Person對象而使用" 的對象,Person的用戶使用工廠函數(factory function,參見條款34)來實例化具體的子類:

// 工廠函數,從一個唯一的數據庫ID
// 創建一個Person對象
Person * makePerson(DatabaseID personIdentifier);

DatabaseID askUserForDatabaseID();


DatabaseID pid = askUserForDatabaseID();

Person *pp = makePerson(pid);    // 創建支持Person
/t/t/t/t // 接口的對象

.../t/t/t      // 通過Person的成員函數
/t/t/t/t // 操作*pp

delete pp;/t/t       // 刪除不再需要的對象

這就帶來一個問題:makePerson返回的指針所指向的對象如何創建呢?顯然,必須從Person派生出某種具體類,使得makePerson可以對其進行實例化。

假設這個類被稱為MyPerson。作為一個具體類,MyPerson必須實現從Person繼承而來的純虛函數。這可以從零做起,但如果已經存在一些組件可以完成大多數或全部所需的工作,那么從軟件工程的角度來說,能利用這些組件將再好不過。例如,假設已經有一個和數據庫有關的舊類PersonInfo,它提供的功能正是MyPerson所需要的:

class PersonInfo {
public:
 PersonInfo(DatabaseID pid);
 virtual ~PersonInfo();

 virtual const char * theName() const;
 virtual const char * theBirthDate() const;
 virtual const char * theAddress() const;
 virtual const char * theNationality() const;

 virtual const char * valueDelimOpen() const;       // 看下文
 virtual const char * valueDelimClose() const;    

 ...

};

可以斷定這是一個很舊的類,因為它的成員函數返回的是const char*而不是string對象。但是,如果鞋合腳,為什么不穿呢?這個類的成員函數名暗示,這雙鞋穿上去會很舒服。

隨之你會發現,當初設計PersonInfo是用來方便地以各種不同格式打印數據庫字段,每個字段值的開頭和結尾用特殊字符串分開。默認情況下,字段值的起始分隔符和結束分隔符為括號,所以字段值 "Ring-tailed Lemur" 將會這樣被格式化:

[Ring-tailed Lemur]

因為括號不是所有PersonInfo的用戶都想要的,虛函數valueDelimOpen和valueDelimClose允許派生類指定它們自己的起始分隔符和結束分隔符。PersonInfo類的theName,theBirthDate,theAddress以及theNationality的實現將調用這兩個虛函數,在它們的返回值中添加適當的分隔符。拿PersonInfo::name作為例子,代碼看起來象這樣:

const char * PersonInfo::valueDelimOpen() const
{
 return "[";/t/t   // 默認起始分隔符
}

const char * PersonInfo::valueDelimClose() const
{
 return "]";/t/t   // 默認結束分隔符
}

const char * PersonInfo::theName() const
{
 // 為返回值保留緩沖區。因為是靜態
 // 類型,它被自動初始化為全零。
 static char value[MAX_FORMATTED_FIELD_VALUE_LENGTH];

 // 寫起始分隔符
 strcpy(value, valueDelimOpen());

 將對象的名字字段值添加到字符串中

 // 寫結束分隔符
 strcat(value, valueDelimClose());

 return value;
}

有些人會挑剔PersonInfo::theName的設計(特別是使用了固定大小的靜態緩沖區 ---- 參見條款23),但請將你的挑剔放在一邊,關注這一點:首先,theName調用valueDelimOpen,生成它將要返回的字符串的起始分隔符;然后,生成名字值本身;最后,調用valueDelimClose。因為valueDelimOpen和valueDelimClose是虛函數,theName返回的結果既依賴于PersonInfo,也依賴于從PersonInfo派生的類。

作為MyPerson的實現者,這是條好消息,因為在研讀Person文檔的細則時你發現,name及其相關函數需要返回的是不帶修飾的值,即,不允許帶分隔符。也就是說,如果一個人來自Madagascar,調用這個人的nationality函數將返回"Madagascar",而不是 "[Madagascar]"。

MyPerson和PersonInfo之間的關系是,PersonInfo剛好有些函數使得MyPerson易于實現。僅次而已。沒看到有 "是一個" 或 "有一個" 的關系。它們的關系是 "用...來實現",而且我們知道,這可以用兩種方式來表示:通過分層(見條款40)和通過私有繼承(見條款42)。條款42指出,分層一般來說是更好的方法,但在有虛函數要被重新定義的情況下,需要使用私有繼承?,F在的情況是,MyPerson需要重新定義valueDelimOpen和valueDelimClose,所以不能用分層,而必須用私有繼承:MyPerson必須從PersonInfo私有繼承。

但MyPerson還必須實現Person接口,因而需要公有繼承。這導致了多繼承一個很合理的應用:將接口的公有繼承和實現的私有繼承結合起來:

class Person {/t/t        // 這個類指定了
public:/t/t/t       // 需要被實現
 virtual ~Person();/t/t  // 的接口

 virtual string name() const = 0;
 virtual string birthDate() const = 0;
 virtual string address() const = 0;
 virtual string nationality() const = 0;
};

class DatabaseID { ... };/t     // 被后面的代碼使用;
/t/t/t/t      // 細節不重要

class PersonInfo {/t/t    // 這個類有些有用
public:/t/t/t       // 的函數,可以用來
 PersonInfo(DatabaseID pid);/t // 實現Person接口
 virtual ~PersonInfo();

 virtual const char * theName() const;
 virtual const char * theBirthDate() const;
 virtual const char * theAddress() const;
 virtual const char * theNationality() const;

 virtual const char * valueDelimOpen() const;
 virtual const char * valueDelimClose() const;

 ...

};


class MyPerson: public Person,        // 注意,使用了
/t        private PersonInfo {  // 多繼承
public:
 MyPerson(DatabaseID pid): PersonInfo(pid) {}

 // 繼承來的虛分隔符函數的重新定義
 const char * valueDelimOpen() const { return ""; }
 const char * valueDelimClose() const { return ""; }

 // 所需的Person成員函數的實現
 string name() const
 { return PersonInfo::theName(); }

 string birthDate() const
 { return PersonInfo::theBirthDate(); }

 string address() const
 { return PersonInfo::theAddress(); }

 string nationality() const
 { return PersonInfo::theNationality(); }
};

用圖形表示,看起來象下面這樣:

/t Person       PersonInfo
/t/t         /
/t/t        /
/t/t       /
/t/t   MyPerson

這種例子證明,MI會既有用又易于理解,盡管可怕的鉆石形狀繼承圖不會明顯消失。

然而,必須當心誘惑。有時你會掉進這樣的陷阱中:對某個需要改動的繼承層次結構來說,本來用一個更基本的重新設計可以更好,但你卻為了追求速度而去使用MI。例如,假設為可以活動的卡通角色設計一個類層次結構。至少從概念上來說,讓各種角色能跳舞唱歌將很有意義,但每一種角色執行這些動作時方式都不一樣。另外,跳舞唱歌的缺省行為是什么也不做。

所有這些用C++來表示就象這樣:

class CartoonCharacter {
public:
 virtual void dance() {}
 virtual void sing() {}
};

虛函數自然地體現了這樣的約束:唱歌跳舞對所有CartoonCharacter對象都有意義。什么也不做的缺省行為通過類中那些函數的空定義來表示(參見條款36)。假設有一個特殊類型的卡通角色是蚱蜢,它以自己特殊的方式跳舞唱歌:

class Grasshopper: public CartoonCharacter {
public:
 virtual void dance();    // 定義在別的什么地方
 virtual void sing();     // 定義在別的什么地方
};

現在假設,在實現了Grasshopper類后,你又想為蟋蟀增加一個類:

class Cricket: public CartoonCharacter {
public:
 virtual void dance();
 virtual void sing();
};

當坐下來實現Cricket類時,你意識到,為Grasshopper類所寫的很多代碼可以重復使用。但這需要費點神,因為要到各處去找出蚱蜢和蟋蟀唱歌跳舞的不同之處。你猛然間想出了一個代碼復用的好辦法:你準備用Grasshopper類來實現Cricket類,你還準備使用虛函數以使Cricket類可以定制Grasshopper的行為。

你立即認識到這兩個要求 ---- "用...來實現" 的關系,以及重新定義虛函數的能力 ---- 意味著Cricket必須從Grasshopper私有繼承,但蟋蟀當然還是一個卡通角色,所以你通過同時從Grasshopper和CartoonCharacter繼承來重新定義Cricket:

class Cricket: public CartoonCharacter,
/t       private Grasshopper {
public:
 virtual void dance();
 virtual void sing();
};

然后準備對Grasshopper類做必要的修改。特別是,需要聲明一些新的虛函數讓Cricket重新定義:

class Grasshopper: public CartoonCharacter {
public:
 virtual void dance();
 virtual void sing();

protected:
 virtual void danceCustomization1();
 virtual void danceCustomization2();

 virtual void singCustomization();
};

蚱蜢跳舞現在被定義成象這樣:

void Grasshopper::dance()
{
 執行共同的跳舞動作;

 danceCustomization1();

 執行更多共同的跳舞動作;

 danceCustomization2();

 執行最后共同的跳舞動作;
}

蚱蜢唱歌的設計與此類似。

很明顯,Cricket類必須修改一下,因為它必須重新定義新的虛函數:

class Cricket:public CartoonCharacter,
     private Grasshopper {
public:
 virtual void dance() { Grasshopper::dance(); }
 virtual void sing() { Grasshopper::sing(); }

protected:
 virtual void danceCustomization1();
 virtual void danceCustomization2();

 virtual void singCustomization();
};

這看來很不錯。當需要Cricket對象去跳舞時,它執行Grasshopper類中共同的dance代碼,然后執行Cricket類中定制的dance代碼,接著繼續執行Grasshopper::dance中的代碼,等等。

然而,這個設計中有個嚴重的缺陷,這就是,你不小心撞上了 "奧卡姆剃刀" ---- 任何一種奧卡姆剃刀都是有害的思想,William of Occam的尤其如此。奧卡姆者鼓吹:如果沒有必要,就不要增加實體?,F在的情況下,實體就是指的繼承關系。如果你相信多繼承比單繼承更復雜的話(我希望你相信),Cricket類的設計就沒必要復雜。(譯注:1) William of Occam(1285-1349),英國神學家,哲學家。2) 奧卡姆剃刀(Occam's razor)是一種思想,主要由William of Occam提出。之所以將它稱為 "奧卡姆剃刀",是因為William of Occam經常性地、很銳利地運用這一思想。)

問題的根本之處在于,Cricket類和Grasshopper類之間并非 "用...來實現" 的關系。而是,Cricket類和Grasshopper類之間享有共同的代碼。特別是,它們享有決定唱歌跳舞行為的代碼 ---- 蚱蜢和蟋蟀都有這種共同的行為。

說兩個類具有共同點的方式不是讓一個類從另一個類繼承,而是讓它們都從一個共同的基類繼承,蚱蜢和蟋蟀之間的公共代碼不屬于Grasshopper類,也不屬于Cricket,而是屬于它們共同的新的基類,如,Insect:

class CartoonCharacter { ... };

class Insect: public CartoonCharacter {
public:
 virtual void dance();    // 蚱蜢和蟋蟀
 virtual void sing();     // 的公共代碼

protected:
 virtual void danceCustomization1() = 0;
 virtual void danceCustomization2() = 0;

 virtual void singCustomization() = 0;
};

class Grasshopper: public Insect {
protected:
 virtual void danceCustomization1();
 virtual void danceCustomization2();

 virtual void singCustomization();
};

class Cricket: public Insect {
protected:
 virtual void danceCustomization1();
 virtual void danceCustomization2();

 virtual void singCustomization();
};

/t   CartoonCharacter
/t/t        |
/t/t        |
/t/t   Insect
/t/t       /
/t/t      /  
/t/t     /    
Grasshopper     Cricket

可以看到,這個設計更清晰。只是涉及到單繼承,此外,只是用到了公有繼承。Grasshopper和Cricket定義的只是定制功能;它們從Insect一點沒變地繼承了dance和sing函數。William of Occam一定會很驕傲。

盡管這個設計比采用了MI的那個方案更清晰,但初看可能會覺得比使用MI的還要遜色。畢竟,和MI的方案相比,這個單繼承結構中引入了一個全新的類,而使用MI就不需要。如果沒必要,為什么要引入一個額外的類呢?

這就將你帶到了多繼承誘人的本性面前。表面看來,MI好象使用起來更容易。它不需要增加新的類,雖然它要求在Grasshopper類中增加一些新的虛函數,但這些函數在任何情況下都是要增加的。

設想有個程序員正在維護一個大型C++類庫,現在需要在庫中增加一個新的類,就象Cricket類要被增加到現有的的CartoonCharacter/Grasshopper層次結構中一樣。程序員知道,有大量的用戶使用現有的層次結構,所以,庫的變化越大,對用戶的影響越大。程序員決心將這種影響降低到最小。對各種選擇再三考慮之后,程序員認識到,如果增加一個從Grasshopper到Cricket的私有繼承連接,層次結構中將不需要任何其它變化。程序員不禁因為這個想法露出了微笑,暗自慶幸今后可以大量地增加功能,而代價僅僅只是增加很小一點復雜性。

現在設想這個負責維護的程序員是你。那么,請抵御這一誘惑!

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产日韩欧美视频| 国产午夜精品视频免费不卡69堂| 欧美亚洲在线播放| 欧美电影电视剧在线观看| 亚洲欧美日韩爽爽影院| 久久久综合免费视频| 亚洲白拍色综合图区| 国产成人精品综合| 色中色综合影院手机版在线观看| 欧美一区二区三区四区在线| 91中文在线视频| 伦伦影院午夜日韩欧美限制| 成人福利网站在线观看11| 亚洲精品在线观看www| 亚洲iv一区二区三区| 久久这里只有精品99| 国产精品中文字幕久久久| 欧美性猛交xxxx| 91精品免费看| 欧美激情综合色| www.日韩.com| 九九精品视频在线| 欧美激情国产日韩精品一区18| 8090成年在线看片午夜| 国产精品福利网| 伊人伊成久久人综合网站| 国产欧美精品一区二区三区-老狼| 亚洲国产美女精品久久久久∴| 国产欧美久久一区二区| 成人黄色av免费在线观看| 国产精品aaa| 日本亚洲精品在线观看| 92裸体在线视频网站| 91在线看www| 亚洲一级免费视频| 亚洲国产婷婷香蕉久久久久久| 欧美性猛交丰臀xxxxx网站| 国产精品高潮呻吟久久av无限| 亚洲精品自拍第一页| 色综合天天综合网国产成人网| 精品国产成人av| 国产精品一区久久| 精品免费在线观看| 欧美麻豆久久久久久中文| 日韩美女在线观看| 社区色欧美激情 | 亚洲三级黄色在线观看| 欧美乱大交xxxxx另类电影| 国产91亚洲精品| 国产日韩精品在线| 成人免费看吃奶视频网站| 欧美尤物巨大精品爽| 亚洲mm色国产网站| 欧美激情高清视频| 成人国产精品日本在线| 国产精品丝袜白浆摸在线| 亚洲影院色在线观看免费| 亚洲成年人影院在线| 国产精品视频久久久| 26uuu亚洲伊人春色| 91爱爱小视频k| 久久不射热爱视频精品| 91国产精品视频在线| 亚洲色图激情小说| 国产精品亚洲自拍| 欧洲美女7788成人免费视频| 国产欧美中文字幕| 亚洲精品成人久久| 日韩欧美在线中文字幕| 国产日韩在线视频| 久久偷看各类女兵18女厕嘘嘘| 97色在线观看免费视频| 成人深夜直播免费观看| 国产精品免费一区二区三区都可以| 欧美激情国产日韩精品一区18| 日本亚洲欧美成人| 久久久久久成人| 欧美亚洲另类激情另类| 91精品国产成人| 久久久久久久久久久免费| 国产亚洲欧洲高清一区| 日产日韩在线亚洲欧美| 91久久在线播放| 国产美女久久久| 精品国产欧美一区二区五十路| 欧美日韩综合视频| 91欧美日韩一区| 菠萝蜜影院一区二区免费| 亚洲性线免费观看视频成熟| 日本免费久久高清视频| 亚洲黄色在线看| 国产精品a久久久久久| 国产日韩欧美另类| 97免费视频在线| 尤物99国产成人精品视频| 美女扒开尿口让男人操亚洲视频网站| 欧美激情一区二区三区久久久| 国产91色在线免费| 成人av色在线观看| 91精品国产91久久久久久最新| 一本色道久久综合狠狠躁篇怎么玩| 国产精品88a∨| 影音先锋欧美在线资源| 亚洲天堂视频在线观看| 国产精品久久久久久久久男| 中文字幕自拍vr一区二区三区| 国产成人中文字幕| 亚洲精品久久久久中文字幕欢迎你| 日韩va亚洲va欧洲va国产| 性欧美在线看片a免费观看| 中文字幕日韩在线观看| xvideos亚洲| 欧美在线视频免费| 中文字幕在线看视频国产欧美在线看完整| 超薄丝袜一区二区| 欧美日本中文字幕| 日韩精品久久久久| 亚洲欧美综合精品久久成人| 久久久国产成人精品| 日韩天堂在线视频| 欧美床上激情在线观看| 91高潮精品免费porn| 色老头一区二区三区在线观看| 欧美大奶子在线| 色悠悠久久88| 亚洲偷欧美偷国内偷| 亚洲欧洲美洲在线综合| 国产欧美日韩中文字幕在线| 国产精品一区二区久久国产| 国内揄拍国内精品| 538国产精品一区二区在线| 亚洲第一福利网| 综合欧美国产视频二区| 欧美激情视频三区| 九色精品免费永久在线| 在线看片第一页欧美| 久久久久久欧美| 日韩在线观看免费av| 欧美国产日韩中文字幕在线| 中文字幕日韩综合av| 国产精品久久久久久久app| 久久精品国产欧美亚洲人人爽| 亚洲精品国产精品国自产在线| 91最新在线免费观看| www.久久色.com| 亚洲理论片在线观看| 欧美日韩国产成人在线| 精品福利樱桃av导航| 日韩av网站在线| 欧美xxxx14xxxxx性爽| 亚洲色图色老头| 一区二区三区高清国产| 欧美一级大片视频| 美女av一区二区| 中日韩美女免费视频网址在线观看| 国产精品一区二区三区免费视频| 国产精品亚洲一区二区三区| 国产成人短视频| 欧美色videos| 国产精品一区二区av影院萌芽| 日韩动漫免费观看电视剧高清| 精品亚洲国产成av人片传媒| 日韩av电影在线免费播放| 热久久99这里有精品|