C++中抽象類和接口類的區別

(這段問答源自:http://blog.sina.com.cn/s/blog_49652a2d0100fk3n.html)//這是大神的回答,有心情可以膜拜下,最後有總結。

Bill Venners:

我在1991至1996這5年間,幾乎一直僅僅使用C++編程。在那時,我認爲多重繼承唯一目的就是讓我能夠從多個基類中繼承它們各自的數據和函數 — 不管是虛擬函數還是非虛擬函數。那時候,我和我使用C++的同事幾乎從未想過可以使用一種不含任何數據而僅包含純虛函數的類,也就是現在Java中被稱爲接口的東西。最近您好像又越來越多地提起了抽象類這個概念,我想問問是不是最近在實驗的過程中發現了一些我們以前未曾注意到的對純接口類進行多重繼承的好處,抑或是您認爲我們以前對抽象類重視得不夠?

Bjarne Stroustrup:

我在對人們解釋這個問題的過程中遇到了很多問題,而且我也一直不能理解爲什麼讓人們理解這個問題是如此困難。自C++出現那天起,就存在着包含數據成員的類和不包含數據成員的類。在過去,人們強調利用一個最基礎的設施以及該設施內部的東西來構造軟件系統,而那個“最基本的設施”通常就是抽象基類。從80年代中葉到80年代末,那些僅由虛擬函數組合而成的類通常都被稱爲ABCs(Abstract Base Classes 抽象基類)。1987年,我在C++中加入了純虛函數的概念,一個純虛函數必須被其派生類重寫。藉助此概念,你可以在一個C++類中通過將其成員函數聲明爲純虛函數的方法表明該類是一個純接口類。從那以後,我就一直強調在C++中,有一種主要的使用類的方法就是讓該類不包含任何狀態,而僅僅作爲一個接口。

從C++的角度來看,一個抽象類和一個接口之間沒有任何區別。有時,我們習慣使用“純抽象類”這個詞來表示某個類僅僅只含有純虛函數(不包含任何數據成員),它是抽象類的最常見的形式。當我試圖向人們解釋這個概念時,我發現如果我不先向他們介紹純虛函數這個語言中被直接支持的概念,人們就很難接受它。有些人僅僅因爲可以在基類中放入一些數據成員,就覺得他們必須這樣做。他們這樣做,就等於構造了經典的不穩定基類,當然同時也就招致該結構所帶來的一切問題。當我向人們介紹C++中直接支持抽象基類的概念時,情況稍微好一些,不過仍然有許多人不能理解它。我認爲這是由於我自身的原因所造成的教育上的失敗 — 我低估了做這件事的難度。這與早些時候Simula社團在理解新概念上的失敗異常相似。有些新概念難以理解,部分原因在於許多人並不是真的想去學習一些全新的東西,他們自以爲自己已經知道了答案。而一旦以爲自己已經知道了答案,再去學一些新東西就會變得非常困難了。在1991年的《The C++ Programming Language》第二版中,有幾個例子描述了抽象類的概念,可不幸的是,我並沒有在全書從頭至尾都貫穿這個思想。

Bill Venners:

使用純抽象類有什麼好處?什麼時候我們應該使用純抽象類而不是使用更爲普遍的多重繼承?

Bjarne Stroustrup:

最明顯的例子就是“多接口、單實現”,這是一種很常見的情況。例如,你的系統也許既需要序列化功能,也需要迭代功能,那麼這兩個功能都可以接口的形式利用抽象類提供。然後,如果需要提供一個支持序列化的容器,你只需要讓容器類繼承序列化抽象類和迭代抽象類就可以了,而這種多重繼承的形式已被Java和C#採納。

另一種通常需要使用多重繼承的情況是僅僅通過多重繼承將手頭的一些類組合起來。它們每一個都沒有特別複雜的語義,將其組合起來完全是出於使用上的方便。當然,你也可以使用委託的模式來完成這個工作,也就是說,你可以在對象中容納一個指向真正實現某些功能的對象指針。這種方法雖然也不錯,但每當你在間接對象中添加一個新方法時,你都需要在自己的類中對應地增加一個新方法。這種做法真讓人頭痛,而且也沒有直截了當地表示出原本的想法,維護起來則更是費時費力。最後一種情況是你需要從兩個類中分別繼承它們各自的狀態。在這種情況下,當這兩個類都非常複雜或它們的語義相互影響時,你很容易陷入混亂之中。然而你可以通過減少過度繼承的方法儘量減少這種情況發生的次數,而當你不可避免地需要使用繼承時,你可以通過儘量減少過度使用多重繼承達到目的,而如果到了連多重繼承都是非要不可的時候,那麼你應該儘量迴避那些複雜的變數。總的來說,在對一個具體問題建立一個模型時,你應該讓該模型儘量簡單,但不致於過分簡單。

有些人經常會說他並不需要多重繼承,因爲所有多重繼承能做的事情都能通過單繼承完成,只是要使用我上面提到的那個名爲“委託”的小技巧而已。更進一步,你也並不需要任何繼承,因爲所有單繼承能夠完成的事都可以通過類之間的轉發完成。實際上,你根本不需要任何類,因爲你完全可以利用指針和數據結構來達到目的。可爲什麼你會想要建立類呢?什麼時候使用語言內建設施比較方便?什麼時候你寧願用一種繞彎的方法呢?我見過有很多場合多重繼承甚至是非常複雜的多重繼承發揮了重要作用。總體上來說,我更喜歡使用語言提供的功能來處理事情。

我們應對複雜情形的另外一種方法是利用模板進行組合。具體而言就是提供多個模板參數,而每個參數都是一個完全獨立的類,它們都是你能夠進行組合的抽象的具體實現。這些類每一個都是完全獨立的,只有最後的派生類才與它們中的每一個存在依賴關係。有時候在一個模板內部根據繼承關係進行組合是很便捷的,而有時則需另想辦法(例如你可以將每一個單獨的類作爲一個數據成員存儲或僅存儲它們各自的指針)。這裏有一個你有時需要從多個類中繼承狀態的例子:你有一個配置器對象,它知道如何處理關於內存的分配和銷燬的問題,你也有一個存取器對象,只要你把內存地址給它,它就能處理關於內存存取的問題。現在,你準備將他們都用於你的一個項目實現中,就讓我們假設是一個操作矩陣的複雜函數吧,此時你至少已經擁有了兩個狀態量,可是並沒有帶來那些對多重繼承心存疑慮的人所擔心的那些問題。基本上,你用一些非常簡單的詞彙就可以將運作的情況解釋清楚。


總結:

首先,我們應該明白,接口和抽象類是面向對象程序設計中的兩個重要概念,是思想層面的東西,不是語言層面的,因此雖然題目中說是c++中抽象類和接口的區別,實際上在其他oo語言也存在相同的思想。

實際上,C++中並沒有明確的接口的定義,與之等價的是純虛類,既只有純虛函數的類,而c++中抽象類的概念是,包含至少一個純虛函數的類。由於java只支持單繼承,所以出現了interface的定義,從而用來模擬多繼承。

可以這樣理解,按抽象程度遞增的順序說就是:普通類->抽象類(java中由abstract修飾的類)->接口(java中interface修飾的類)。

參考《Effective C++》條款31和條款34:

c++中interface class通常不帶成員變量,也沒有構造函數,只有一個visual析構函數以及一組pure visual函數,用來敘述整個接口。雖然類似Java和.net的interface,但是C++的interface class並不需要復旦Java和.net的interface所需負擔的責任。例如:Java和.net都不允許在interface內實現成員變量或成員函數,但是C++不禁止這兩樣東西。

這裏順帶說明接口繼承和實現繼承:

所謂接口繼承就是派生類只繼承函數的接口,也就是聲明。而實現繼承就是派生類同時繼承函數的接口和實現。

聲明一個純虛函數(pure visual)的目的就是爲了讓派生類只繼承函數接口,即接口繼承。

聲明一個非純虛函數(impure visual)的目的是爲了讓派生類繼承函數接口和缺省實現。

聲明一個非虛函數(non visual)的目的是爲了讓派生類繼承函數接口和一份強制實現。

推薦一篇很不錯的博文:抽象類與接口的區別

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章