程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> Effective C++ 43,44

Effective C++ 43,44

編輯:C++入門知識

Effective C++ 43,44


43.明智地使用多繼承。

多繼承帶來了極大的復雜性。最基本的一條就是二義性。

當派生類為多繼承時,其多個基類有同名的成員時,就會出現二義性。通常要明確其使用哪個成員的。顯式地限制修飾成員不僅很笨拙,而且會帶來限制。當顯式地用一個類名來修飾一個虛函數時,函數就會被固定,而不再具有虛擬的特性。對於虛函數,若兩個基類擁有一個同名同參的虛函數,當派生類沒有重新定義虛函數時(可以只聲明),直接調用這個同名函數會出二義性錯誤,需要指明其類。而當派生類中重新定義了這個函數,這是不可能的,因為一個類只允許有唯一同參同名的函數(其實不對,函數體聲不聲明const,還是不同的,一個用於正常對象,一個用於const對象)。而對於派生類中重新定義虛函數,其實是在派生類中重新創建了一個函數,在這裡要重新定義兩個虛函數是不行的,因為重定義一個函數就是在創建一個函數,而一個函數裡不能有兩個同名同參的函數。

當不修改虛函數時,只要用指明基類的方式去調用基類的函數即可,當需要重新定義一個虛函數時,即對於派生類只保留一個虛函數時,不用關心其保留哪個虛函數,只要正常的重新聲明定義即可。

而當需要重新定義多個虛函數,且派生類要用到這多個虛函數時,一種所謂的巧妙的方法解決二義性,就是在存在二義性的兩個基類下再進行派生,在這兩個派生類中給其定義各自的新名字,而函數體為內聯的調用基類的函數。而多重繼承的派生類多重繼承與這兩個中間類,就將兩個原本沖突的基類函數變成了兩個不沖突的基類函數。僅僅為重新定義一個虛函數,而不得不引入新的類。

class A{
public:
	virtual void fun(){cout<<"A"<fun();//就會調用基類的虛函數,而不會發生沖突
	B* bbb = d;//完全沒有體現到多態
	bbb->fun();
	//d->fun();//而直接調用會二義性。
	C* c =new C();
	//c->fun();//改名後原來的這個函數還是二義性的。
	A* aa = c;
	aa->fun();//輸出 A in C
	B* bb = c;//輸出 B in C
	bb->fun();
	AuxA *a = c;
	AuxB * b = c;
	a->fun();//輸出 A in C
	b->fun();//輸出 B in C
這樣就同個改名而在派生類中重新定義了兩個基類的同名函數。


而除了二義性,還經常碰到的問題就是菱形繼承,也就是一個基類被繼承多次,但是否應該保存多個拷貝的問題。一般來說都是只擁有一個這樣的基類,即將其聲明為虛基類。

但這樣也是有問題的,首先程序開發這設計一個基類A派生了多個基類BC,但是在其定義BC時無法知道以後是否有人會多繼承BC,而後人想要修改BC的定義使其虛繼承於類A又是很難做到的,一般ABC都是只讀的庫函數,而D由庫的用戶開發。另一方面,如果A聲明為BC的虛基類,這在大部分情況下會給用戶帶來空間和時間上的額外消耗。

而對於虛基類,若A為非虛基類,則D的對象在內存中的分配通常占用連續的內存單元,而若A為虛基類,會包含一個指針指向虛基類數據成員的函數單元。這裡對於A派生BC,而D繼承與B和C,則D的內存中有兩個A,而如果是虛基類,D中有兩個指向A的指針。

所以考慮這些,進行高效的類設計時,若涉及到MI 多繼承,作為庫的設計者就要具有超凡的遠見。

向虛基類傳遞構造函數參數。對於單繼承中,派生類在成員初始化列表中對基類傳遞參數,由於是單繼承的,這些參數可以逐層的向上傳遞。但虛基類的構造函數就不同了,它的參數由繼承結構中最底層的派生類的成員初始化列表來指定,如果有新類增加到繼承結構中,可能要修改執行初始化的類。避免這個問題的辦法是消除對虛基類傳遞構造函數參數的需要,最簡單的就是java中的解決方法,即避免在虛基類中放入數據成員,java中的虛基類 接口禁止包含數據。

虛函數的優先度。當虛函數在多重繼承中涉及到虛基類時,會有優先度的問題,仍然以ABCD為例,A中有虛函數void fun(),C中重定義了fun,但B和D中沒有,調用D的指針fun時,若A不是虛基類,則是正常的情況,發生二義性錯誤。但是當A是虛基類時,就可以說C中重定義的fun的優先度高於最初的A中的定義也是B中的fun,則此時會無二義性的解析為調用 C::fun()函數。

當對於原 B類繼承於A類,現今需要新加入一個新類C繼承與A,但是你發現其與B類用許多相似的地方,但C又不是一個B,所以你決定讓C 由B實現,便讓C私有繼承於B,同時繼承A,同時修改了一下B中的虛函數,實現了多繼承。而另一種做法是將BC的共同點放在一個新的類D中,改變繼承結構,使D繼承於A,而BC繼承於D,這樣就只有單繼承了。表面上看來,多繼承沒有添加一個新的類,沒有改變原有的繼承結構,它只是在原有的類B的基礎上增加了一些新的虛函數,這樣看似增加了大量的功能,而只增加了一點點復雜性。但事實讓,引入多繼承就會帶來許多麻煩。

MI是復雜的,但也是有用的,需要明智的去使用。


44.說你想說的,理解你想說的。

簡單來說,理解面向對象構件在c++中的含義,而不是只去記憶c++的語言規則。對c++理解越深,越能清晰的考慮問題。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved