C++中的接口繼承和實現繼承
不少人認爲,C++中是不存在接口繼承的,只有Java、C#這類語言才提供了相應的語法支持。設計模式
可是,如同魯迅說過的某句名言:世上本沒有接口繼承,用的人多了,纔有了接口繼承。C++中依然能夠實現接口繼承,只是形式上稍有不一樣罷了。app
C++中的繼承基於一個事實:父類定義的成員函數會一直被子類繼承(包括被子類隱藏的部分)。函數
而父類中提供的函數能夠有三種:1)普通成員函數 2)普通虛函數 3)純虛函數。這三種函數類型表明了三種繼承設計模式。spa
一個簡單的實例代碼以下:設計
04 |
virtual void Draw() = 0; |
05 |
virtual int GetError(); |
09 |
class Rectangular : public Shape |
14 |
class Circle : public Shape |
普通成員函數由父類聲明且實現,子類應繼承接口以及強制性的實現。code
這幾乎是最多見的一種函數類型,表明了典型的」is-a」繼承設計模式。對象
ps:所謂的」is-a」設計模式,指的是」everything that applies to base classes must also apply to derived classes」繼承
示例中,函數GetId嚴格遵照」is-a」模式。由於每一個子類本質都是一個Shape對象,都有一個惟一的ID接口
普通虛函數能夠在父類中有默認的實現,而這個默認實現能夠由子類繼承。
子類也能夠選擇重寫虛函數以實現多態性。資源
因此,普通虛函數在繼承設計中表示派生類必須支持此接口,可是否重寫,由派生類本身決定。
如同每一個子類對象都應該有一個報錯函數。可是函數可使用父類提供的默認實現(提示簡單的出錯信息,而後清理資源),也能夠選擇本身實現(每一個子類有本身的錯誤語義)
純虛函數會使得父類自動成爲不可實例化的抽象類。並且每一個繼承的子類必須強制自行重寫。
因此,純虛函數表示子類繼承父類的函數接口,而且必須本身具體實現該函數。
即從這個角度上看,純虛函數表明的就是接口繼承。
實例代碼中,父類將Draw聲明爲純虛函數。這代表每一個具體的子類都應該有Draw函數,而且須要本身實現(每一個具體子類的Draw實現應是不一樣的)。
對於純虛函數,有一個有意思的特性:純虛函數能夠有實現代碼
之因此說這個特性有意思,是由於擁有純虛函數的類不能實例化而且純虛函數指定的是接口繼承,子類仍然須要本身實現函數。
這就引起了一個問題:如何調用這個純虛函數的默認實現版本?解決的方法是顯式調用
1 |
Shape* p = new Rectangular; |
4 |
// invoke defaulat implementation of the pure-virtual function |
7 |
// doing something later |
那麼這種讓人以爲操蛋的trick有沒有什麼應用呢?
假設你有一個父類F,定義了一個普通虛函數,子類A,B都使用默認的虛函數實現。可是某天你須要增長一個新的子類C,可是這個子類不能使用默認的實現,必須重寫。
不幸的是,你忘了重寫這個函數,因此編譯器爲你調用了默認實現,因而意外的結果讓你蛋碎了一地。
很明顯,使用帶有實現的純虛函數就能夠解決這個問題。純虛函數會強制要求你重寫虛函數,而你也能夠在須要默認實現時經過顯式調用完成相應工做。
不過須要這種trick的狀況至關罕見,並且多半是設計出了問題或者徹底能夠人爲避免。