程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++編程批評系列 繼承的本質

C++編程批評系列 繼承的本質

編輯:關於C++

Eiffel和C++都提供了多繼承的機制。但Java卻沒有,因為它認為多繼承會導致許多問題的出現。不過Java提供了接口(interface)作為一種替換機制,它類似於Objective C中的協議(protocol)。Sun宣稱接口可以提供多繼承所能提供的所有特性。

Sun所宣稱的“多繼承會帶來許多的問題”這個觀點是對的,尤其是在C++中用以實現多繼承的方法更能說明這一點。那些看起來似乎使用多繼承會比單繼承更簡單的理由,現在都以被證明是毫無意義。例如,如何制訂對於從兩個類之上繼承得到的具有相同名字的數據項之間的策略?它們之間是否兼容?如果是的話,那他們是否應該被合並成為一個實體?如果不兼容,那應該如何區分它們?……這樣的列表可以列出很長很長。

Java的接口機制也可以用以實現多繼承,但它也有一個很重要的不同之處(與C++相比):繼承中的接口必須是抽象的。由於使用接口並沒有任何的實作,這就消除了需要從不同實作之間選擇的可能。Java允許在接口中聲明具有常數字段。當需要多繼承時,他們就合並成為一個實體,這樣也就不會導致歧義的產生。但是,當這些常數具有不同的值時,又有什麼會發生呢?

由於Java不支持多繼承,我們就不可以像在C++和Eiffel中那樣使用混合(mixin)了。混合是一種特性,它可以把從不同的類中得到的不同的非抽象的函數放到一起形成一個新的復雜的類。例如,我們可能希望從不同的源代碼中導入一些utility函數。然而,我們也可以通過使用組合而不是繼承來達到同樣的效果,因此,這也就不會對Java構成一個重要的攻擊了。

Eiffel在解決多繼承問題時並沒有導入一個單獨的接口機制。

有些人可能認為,相對於多繼承來說,單繼承更優雅一些。這是一個很特別的觀點。

BETA [Madsen 93]就屬於認為“多繼承不優雅”的那一種:“Beta中沒有多繼承,這主要是因為(對於多繼承)缺乏一個深刻的理論上的理解,並且當前的(對於多繼承的)建議在技術上看來也非常復雜”。他們引用了Flavors(一種可以將類混合在一起的語言)為證據。與Madsen相比,Flavors中的多繼承與其順序有關,也就是說,繼承自(A,B)和繼承自(B,A)是不一樣的。

Ada95是另一種不支持多繼承的語言。Ada95支持單繼承,並把它叫做標記類型擴展(tagged type extension)。

另外一些人認為,對於某些特殊模型下的問題,多繼承可以提供優雅的解法,因此為之付出的努力也是值得的。雖然上面所列出的關於多繼承的問題列表並不完善,它仍然顯示:與多繼承相關的問題是可以被系統地辨識出來的,而一旦問題被確認,它們也就可以被優雅地解決。當[Sakkinen 92]對於多繼承研究到達一個很深的程度後,它就得出了上述定義。

Eiffel中采用的方法是,多繼承會引發一些有趣的且有挑戰性的問題,然後再優雅地解決它們。程序員所需做的所有決定都被限制在類的繼承子句中。它包括使用renaming來保證眾多從繼承中得來的同名特性最終成為具有不同名字的特性,對於繼承而來的特性所施展的新的export策略:redefining和undefining,以及用來消除歧義的select。在所有的情況下,編譯器都會為我們做好這一切,為了使得語義清晰而不管是選擇使用fork或是join,程序員都具有完全的控制權。

C++中相對Eiffel來說有著另外一種不同的用於消除歧義的機制。在Eiffel中,在renames子句中,特性間必須有著不同的名字。在C++中,可以使用域解析操作符’::’來區分成員。Eiffel的做法好處在於,歧義在聲明中就被消除掉了。Eiffel的繼承子句相對C++的來說要復雜不少,但它的代碼也顯得更簡單,更穩固,並更具彈性。這也就是聲明方法與操作符方法相比的好處所在。在C++中,每次當我們碰到在多個成員間具有歧義時,我們必須在代碼中使用域解析操作符。這經使得代碼變得混亂不堪,影響其延展性,如果有其他地方的改變會影響歧義時,我們可能就需要在歧義可能出現的每個地方改變已有的代碼。

依照[Stroustrup 94]中12.8節所說,ANSI委員會考慮過使用renaming,但是這個提議被委員會中的一個成員所阻塞掉了,他堅持讓委員會中的其他成員用兩周時間來好好地考慮這個問題。在12.8節中給出的例子顯示了在沒有顯示的renaming的前提下,如何做可以得到同樣的效果。問題在於,如果這都需要那些專家們使用兩周來考慮如何實現,那留給我們的空間又有多少呢?

域解析操作符並不只是被用來消除多繼承所帶來的歧義。由於設計良好的語言可以避免歧義的出現,因此域解析操作符也就是一個丑陋的,加深復雜性的實作手法。

在C++中,“如何來聲明多繼承中的父類們”是一個很復雜的問題。它影響到了建構函數被調用的次序,當程序員確實想從子類轉到父類時也會導致問題的出現。然而,我們也可以把這個稱為不好的程序設計風格。

C++和Eiffel的另一個不同之處在於直接的重復繼承,Eiffel中允許:

class B inherit A, A end 但

class B : public A, public A { };

卻不被C++認可。

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