【40】明智而審慎地使用多重繼承

Andy Niu發表於2014-01-03

1、多種繼承,首先帶來一個問題。那就是子類會從多個父類中繼承相同的名稱。這樣,在呼叫的時候,必然導致歧義。

2、考慮Base1中有個public方法XXX,Base2中有個private方法XXX,Derived繼承Base1和Base2,那麼分析一下,Derived d; d.XXX()到底呼叫哪個方法?

  你可能會想,雖然兩個都匹配,但是隻有Base1的XXX方法可訪問,因此呼叫Base1的XXX方法。這種想法是錯的,編譯報錯:對XXX的訪問不明確。為什麼?

  因為編譯器決議呼叫哪個方法的時候,第一步,先把所有匹配的名稱都拿來,不管是不是可以訪問,然後在裡面找到一個最優匹配,如果兩個匹配程度一樣,編譯器就傻眼了,只能報錯。再退一步說,C++過載不能跨域,即只能在當前類中過載。經過測試,從多個父類繼承相同的名稱,即使匹配程度不一樣,也會導致對XXX的訪問不明確。

  怎麼解決這個問題呢?

  明確告訴編譯器,我要呼叫哪個父類的方法。

  d.Base1::XXX(); //OK

  d.Base2::XXX(); //Error,無法訪問成員XXX

  注:單一繼承也可以使用d.Base1::XXX();這種形式。

  還有一個辦法,子類定義一個XXX方法,隱藏兩個父類的XXX方法,這樣就不存在不明確的呼叫了。

3、鑽石型多繼承,考慮InputFile繼承File,OutputFile繼承File,IOFile繼承InputFile和OutputFile,File中有個資料fileName,現在出問題了。從繼承角度講,IOFile有兩份fileName,但根據常識,IOFile應該只有一個fileName。C++預設的做法是IOFile中有兩個fileName。

4、如何解決上面的問題呢?也就是說,保證IOFile中只有一個fileName。通過使用virtual繼承。讓InputFile和OutputFile使用virtual繼承,IOFile的繼承方式不變。這樣就使得IOFile中,只有一個fileName。顯而易見,這種做法非常醜陋。而且,雖然起到了效果,要付出很大的代價。編譯器在幕後做了一些事情,導致的結果是:InputFile的體積變大,訪問變慢,初始化也可能帶來詭異的問題。

5、因此,儘量少使用virtual繼承。如果必須使用virtual繼承,也要保證不在virtual Base(即File)中放置資料,從而保證IOFile中沒有重複的資料。C#和Java中的interface就不允許有資料。相對而言,有重複的方法,還好辦,可以指定呼叫哪個父類的方法。

6、那麼,多重繼承就一無是處嗎?多重繼承的使用場景:public繼承一個類,暴露介面,同時private繼承一個類,根據它來實現。你可能會想,根據某物實現出,不是應該優先使用複合技術嗎?private繼承可以解決兩個問題:一是private繼承可以訪問protected成員,二是可以重寫方法。

相關文章