[原創文章歡迎轉載,但請保留作者信息]
Justin
于 2010-02-23
當然ITEM38其實也沒什么需要多記的,ITEM32中說到了“是一個”的模型,這里要回顧的是“有一個”的模型。(原文中有兩種說法,has a和implement in terms of,然而個人竊以為意思其實差不多,在筆記里就不鉆牛角尖了……)
在“是一個”模型不適用于構建一個類或是一種關系的時候,可以考慮“有一個”。大師給的例子應該是被舉例舉爛了的:一個人“有一個”地址,“有一個”電話號碼,諸如此類。
與“是一個”不同,“有一個”模型不能用公有繼承實現,而是以類成員的方式構造的,這個道理也很淺顯:類與其成員之間的關系本來就是“有一個”。
到了第39條軍規,講的是和私有繼承有關的事情。
公有繼承中的子類對象是可以被轉換為它的父類對象的(“是一個”的關系),而私有繼承中這種轉換是不成立的。
另外一點,私有繼承中父類的所有公有和保護成員(public和protected)到了子類中,都變成了私有成員。
因為上面的特性,私有繼承并不滿足“是一個”模型的需要。更可憐的是,私有繼承并不能代表一種設計思路(公有繼承代表了“是一個”的模型設計),而僅僅是“有一個”模型的一種實現手段(私有繼承過來的所有成員都是私有的,從這個角度來說它就只是“實現”)。
另一種手段大師在Item38中有提過,就是用類成員的方式來構造,名曰composition。
既然兩者都能實現“有一個”模型,那么如何選擇呢?能用composition就用composition,必需私有繼承的時候方才私有繼承。
比如我們有個AClass:
class AClass{
public:
virtual void Interface_1(/*..*/);
};
public:
virtual void Interface_1(/*..*/);
};
以下為私有繼承:
class BClass : private AClass{
private:
virtual void Interface_1(/*..*/);
//..
};
private:
virtual void Interface_1(/*..*/);
//..
};
而下面的composition可以達到一樣甚至更好的效果:
class AnotherAClass: public AClass{
public:
virtual void Interface_1(/*..*/);
//..
};
class DClass{
private:
AnotherAClass* a;
//..
};
public:
virtual void Interface_1(/*..*/);
//..
};
class DClass{
private:
AnotherAClass* a;
//..
};
【以上代碼純粹是對大師例程的簡陋抄襲,大師見諒……】
BClass和DClass都實現了“有一個”,但相比之下還是能分辨出長短:
-
DClass中的AnotherAClass是私有成員,除了它自己沒有人能夠訪問修改;而私有繼承的BClass不能保證其“擁有”的AClass實現部分不會被第三者修改,即使是私有繼承來的。(為什么這么說?看下去……)
BClass私有繼承了AClass,相當于它“有了一個”AClass可以用,可以玩。AClass中的公有/保護成員都變成了BClass的人,但是在享受使用這些成員的同時,BClass還要承擔提供這些成員給別人服務的義務。
ITEM35中曾經提到:虛擬函數機制和公有/私有/保護體制是沒有任何關系的。因此在例子中的Interface_1有可能在以下的情況中被替代然后“調用”: - 一個CClass公有繼承了BClass
- CClass定義了自己的Interface_1版本
-
有一個BClass的指針,指向一個CClass的對象,某個操作中調用了Interface_1(CClass的實現版本)
很曲折哈?希望我下次讀的時候還能看懂@#¥%
-
DClass由于只是定義了一個指向AnotherAClass的指針,那么在定義DClass的文件中就不需要include AClass或AnotherAClass的頭文件。于是就避免了編譯依賴(compilation dependecies)
而BClass因為是繼承了AClass,在BClass的文件中就需要加上AClass的頭文件,也就不可避免的產生了編譯時的依賴。
對于EBO(Empty Base Optimization)的情況,私有繼承就顯現出了它的優勢。
所謂EBO就是這樣的一種情況,有一種特殊的類,它沒有非靜態數據成員(non-static data member),也沒有虛函數(于是不會需要空間存儲虛表)。
所以這樣的一種類其實不占用任何空間,不過因為C++不允許0字節的對象存在,而且很多編譯器都會添加額外的空間來實現字節對齊,于是這種特殊的類的實際大小應該是1個char對象的大小。
在這種類中,往往會有很多typedef,enum,靜態數據成員或者是非虛函數。所以他們還是有價值的。
需要在“有一個”關系中利用這種類的時候,如果采用composition,那么根據上面的結論,就需要付出額外的空間來“存放”這個本來不占空間的類。
然而如果是私有繼承呢,就可以避免這種情況。
(最后是我的想法:這么精打細算……現在貌似很少人會用到這個EBO了吧?如果真的需要,就再回到原書中看看例子吧。)