(源自: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#采納。
另一種通常需要使用多重繼承的情況是僅僅通過多重繼承將手頭的一些類組合起來。它們每一個都沒有特別復雜的語義,將其組合起來完全是出於使用上的方便。當然,你也可以使用委托的模式來完成這個工作,也就是說,你可以在對象中容納一個指向真正實現某些功能的對象指針。這種方法雖然也不錯,但每當你在間接對象中添加一個新方法時,你都需要在自己的類中對應地增加一個新方法。這種做法真讓人頭痛,而且也沒有直截了當地表示出原本的想法,維護起來則更是費時費力。最后一種情況是你需要從兩個類中分別繼承它們各自的狀態。在這種情況下,當這兩個類都非常復雜或它們的語義相互影響時,你很容易陷入混亂之中。然而你可以通過減少過度繼承的方法盡量減少這種情況發生的次數,而當你不可避免地需要使用繼承時,你可以通過盡量減少過度使用多重繼承達到目的,而如果到了連多重繼承都是非要不可的時候,那么你應該盡量回避那些復雜的變數。總的來說,在對一個具體問題建立一個模型時,你應該讓該模型盡量簡單,但不致於過分簡單。
有些人經常會說他並不需要多重繼承,因為所有多重繼承能做的事情都能通過單繼承完成,只是要使用我上面提到的那個名為“委托”的小技巧而已。更進一步,你也並不需要任何繼承,因為所有單繼承能夠完成的事都可以通過類之間的轉發完成。實際上,你根本不需要任何類,因為你完全可以利用指針和數據結構來達到目的。可為什么你會想要建立類呢?什么時候使用語言內建設施比較方便?什么時候你寧願用一種繞彎的方法呢?我見過有很多場合多重繼承甚至是非常復雜的多重繼承發揮了重要作用。總體上來說,我更喜歡使用語言提供的功能來處理事情。
我們應對復雜情形的另外一種方法是利用模板進行組合。具體而言就是提供多個模板參數,而每個參數都是一個完全獨立的類,它們都是你能夠進行組合的抽象的具體實現。這些類每一個都是完全獨立的,只有最后的派生類才與它們中的每一個存在依賴關系。有時候在一個模板內部根據繼承關系進行組合是很便捷的,而有時則需另想辦法(例如你可以將每一個單獨的類作為一個數據成員存儲或僅存儲它們各自的指針)。這里有一個你有時需要從多個類中繼承狀態的例子:你有一個配置器對象,它知道如何處理關於內存的分配和銷毀的問題,你也有一個存取器對象,只要你把內存地址給它,它就能處理關於內存存取的問題。現在,你准備將他們都用於你的一個項目實現中,就讓我們假設是一個操作矩陣的復雜函數吧,此時你至少已經擁有了兩個狀態量,可是並沒有帶來那些對多重繼承心存疑慮的人所擔心的那些問題。基本上,你用一些非常簡單的詞匯就可以將運作的情況解釋清楚。