“瑜珈山夜話”--- 尋根究底談“繼承”(一) (轉)
摘要:繼承是C++的一個很重要的特性,也是OO的三大特徵之一,希望對此做一個簡單的論述,能消除你一些困惑。
繼承是什麼?
繼承是將相關的類組織起來,並分亨其間的共通資料和操作行為的一種方法,同時也要注意到繼承關係是一種強耦合的關係。
繼承的目的是什麼?
說到繼承的目的,人們總是會想到程式碼重用,實則不然,程式碼重用只不過是繼承的一個副作用,繼承的主要目的是表達一個外部有意義的關係,該關係描述了問題域內的2個實體之間的行為關係。換句話說,繼承是因問題域的現實性而產生的,並不是由於解域內的技術目的而出現的。
繼承的障礙是什麼?
繼承的使用並不像我們想象的那麼簡單,在決定繼承的時候,有很多語言特性會構成一定的障礙。
1、非虛成員的存在。
如果我們確定了一個基類中的某個成員函式是非虛的,那就意味著這個函式在派生類中不應該被重新定義,如果你重新定義了,所得的結果很可能不是你所期望的,例如:
class A
{ public: void f() { cout< class B: public A
{ public: void f() { cout< A* pA=new B;
pA->f();
delete pA;
這裡,我們可能期望pA->f()會輸出B::f,但是實際上是A::f,當然,如果把它宣告為virtual就沒有問題了,關鍵是我們怎麼能夠明確確定那個函式應該宣告為virtual呢?如何使基類能夠完全預測到子類的各種需求?毫無疑問,這是一個挑戰!也許把所有的基類成員函式都宣告為virtual是一個簡單的解決辦法,但是這樣做會大大降低的,對於如此注重效率的C++來說,這麼做是對它的一個背叛,C++更希望我們只把那些需要重定義的函式宣告為virtual。
2、基類成員的過度保護
封裝是一個很好的特性,但是封裝的度很難掌握,例如:
class A
{ private: class P { ...}; };
class B : public A::P { ... };
有的程式設計師馬上就會意識到這是一個錯誤:無法獲取A::P,因為它的是Private!當然這裡只需要把private改為protected就可以了,但是問題的關鍵在於基類如何預測到子類需要繼承的類究竟是什麼?同上一個障礙一樣,這也是一個挑戰。天真的程式設計師可能以為只要把基類中所有的成員都宣告為public/protected就萬事大吉了,但是實際上如果我們的類釋出之後,public/protected的成員就再也無法改變,否則勢必會中斷客戶的程式碼,這就要求我們儘量把實現細節封裝為private的,只把那些子類需要變動的成員宣告為public/protected許可權(虛擬函式可以宣告為private的,這是一個例外),但是對基類的設計者要求如此之高,也是非常困難的。
3、基類中模組化設計不足
模組化會使程式更加簡潔、有效,但是對於基類來說,要做到有效的模組化並不容易。例如我們有一個二分查詢樹BSTree,定義如下:
template
class BSTree
{
private:
class Node
{
public:
T t;
Node* left;
Node* right;
Node(const T& _t):t(_t){ }
...
};
Node* ;
...
public:
void insert(const T& t);
...
protected:
virtual void doinsert(const T& t, Node*& n);
...
};
template
void BSTree
{
if(n==0) n=new Node(t);
else
{
if(t
else doinsert(t, n->right);
}
}
現在呢,我們要定義一個紅黑樹,定義如下:
template
class RBTree: public BSTree
{
protected:
class Node: public BSTree
{
public:
bool is_red;
Node(const T& t);
};
void doinsert(const T& t, BSTree
virtual void rebalance(Node* n);
...
};
template
void BSTree
{
if(n==0)
{
Node m=new Node(t);
n=m;
rebalance(m);
}
else
{
if(t
else doinsert(t, n->right);
}
}
我們發現BSTree::doinsert和RBTree::doinsert程式碼大致相同,這就存在著複製程式碼操作,我們知道程式碼複製工作十分乏味、易出錯、程式碼臃腫、維護困難...所以一個好的基類應該使派生類儘量少的複製程式碼,最好不復制。看看我們的基類:很多二分查詢樹都需要建立不同的節點,也有rebalance操作。好了,我們應該對基類BSTree作如下修改:
Template
class BSTree
{
protected:
virtual Node* new_node(const T& t)
{ return new Node(t); }
virtual void rebalance(Node* n) { }
...
};
這時候doinsert改動如下:
template
void BSTree
{
if(n==0)
{
n=new_node(t);
rebalance(n);
}
else
{
if(t
else doinsert(t, n->right);
}
}
這時候派生類RBTree定義改為:
template
class RBTree: public BSTree
{
protected:
Node* new_node(const T& t)
{ return new Node(t); }
void rebalance(BSTree
{ ... }
...
};
這樣一來,程式設計師就無需複製程式碼了。我們發現,如果要使派生類的客戶永遠不復制程式碼,那麼就要把派生類需要改變的程式碼分離出來,形成一個單獨的模組函式(虛),但是在我們沒有足夠的派生類的資訊的時候,這樣做是不可能的,就算可能,難度也是相當得高,同時,大量的虛擬函式也會降低程式的執行效率。
4、friend關鍵字的過分使用
這個問題的根源在於友員關係的不繼承性。我們仍然用上面的例子,不過做一下變動:
template
template
class BSNode
{
protected:
T t;
BSNode(const T& t);
friend class BSTree
};
template
class BSTree
{
...沒有了nested Node類
};
這裡,由於BSNode的實現屬於BSTree的實現細節,同時為了防止BSNode派生類偶然存取BSNode的成員,所以我們把他的所有成員都宣告為Protected,同時讓BSTree稱為它的友員。但是由於RBTree要存取BSNode的成員,再加上友員的非繼承,使事情變得複雜起來,通常有2種辦法解決這個問題:
1、將BSNode的成員宣告為public,但是這樣一來friend也就沒有什麼意義了。
2、在RBNode類中增加一個存取函式,但是和不用friend相比,麻煩多了。
另外還有一些其它的抉擇也是讓人頭疼,例如:基類中的成員變數過多,繼承的屬性選擇等。
未完(待續...)
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-960904/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- “瑜珈山夜話” ----記憶體分配(一) (轉)記憶體
- “瑜珈山夜話”--- 參考資料 (轉)
- “瑜珈山夜話” ----記憶體分配(三) (轉)記憶體
- “瑜珈山夜話” ----記憶體分配(二) (轉)記憶體
- 粗談繼承繼承
- 如何繼承Date物件?由一道題徹底弄懂JS繼承。繼承物件JS
- 刨根究底之CategoryGo
- Javascript繼承,再談JavaScript繼承
- 淺談JS的繼承JS繼承
- 徹底搞懂JavaScript中的繼承JavaScript繼承
- 徹底弄懂JS原型與繼承JS原型繼承
- C++繼承一之公有繼承C++繼承
- odoo 繼承(owl繼承、web繼承、view繼承)Odoo繼承WebView
- 淺談JavaScript中的繼承JavaScript繼承
- 白話JavaScript原型鏈和繼承JavaScript原型繼承
- 徹底搞懂原型、原型鏈和繼承原型繼承
- 繼承與介面 (轉)繼承
- 原型,繼承——原型繼承原型繼承
- 菱形繼承,虛繼承繼承
- JavaScript的繼承-轉載JavaScript繼承
- 白馬非馬----繼承 (轉)繼承
- 多繼承 與 多重繼承繼承
- C++繼承詳解:共有(public)繼承,私有(private)繼承,保護(protected)繼承C++繼承
- java 繼承的基礎(轉)Java繼承
- 三種繼承的方法:public 繼承/private繼承/protected繼承詳解及區別繼承
- Javascript繼承4:潔淨的繼承者—-原型式繼承JavaScript繼承原型
- Javascript繼承2:建立即繼承—-建構函式繼承JavaScript繼承函式
- css可繼承屬性和非繼承屬性一覽CSS繼承
- Java基礎之淺談繼承、多型Java繼承多型
- 繼承繼承
- C++高階教程之繼承得本質:單繼承(一)C++繼承
- JS原型繼承和類式繼承JS原型繼承
- C++中公有繼承、保護繼承、私有繼承的區別C++繼承
- 公有繼承、私有繼承和保護繼承之間的對比繼承
- 虛擬繼承的意義 (轉)繼承
- 淺談JS物件的建立、原型、原型鏈繼承JS物件原型繼承
- ? 一文看懂 JS 繼承JS繼承
- C#中的繼承(一)C#繼承