C++中的多重繼承
雖然在軟體設計中,許多書籍都推薦優先使用組合而不是繼承,然而繼承仍然擁有許多天然的優勢,對基類成員的自動擁有,而不用像組合要顯示地去轉向調用所需複用的成員,從而平添更多的代碼。
多重繼承在某些情況下,可以使我們的設計具有更多的靈活性,下面我們討論一些多重繼承中的問題及解決辦法。
我們實現了一個抽象基類A,然後由此派生了出了諸多的實作類別,如A1,A2,A3,在項目的起初,這些A的具體類工作很好,我們的軟體模組也依賴於這一個抽象基類A。一切都很好。隨著項目的進行。我們又進入了另一個模組的開發。也許起先的考慮不周,也許設計師在設計時出現了其他什麼,這裡我們又要使用這些A1,A2,A3了。但是我們也發現,抽象基類A的這些介面方法已經不能滿足這一個模組的功能要求了,在這個新的模組中,我們需要另一些通用的方法幹其它的一些事情。怎麼辦?我們要重寫A1,A2,A3,並且加入在新模組中所需要的這些通用的方法嗎?但是根據軟體開發的介面依賴原則,我們的軟體模組還能夠依賴於抽象基類A嗎?可是這些新增的通用方法並未在A中聲明。或許我們應該考慮一下多重繼承,將新增的通用方法抽象到一個新的介面B中。這樣我們在使用A1的新增方法時,只需依賴於這個新的介面B,而在使用A1以前的方法時,只需依賴於介面A。
真是個好主意。於是新的衍生類別實現了。A11繼承於A1和B,A22繼承於A2和B,A33繼承於A3和B,應用環境如下:
bool App::AddSub(B* b)
{
A* a = dynamic_cast<A*>(b); //動態轉換為A;
If (a!=0)
a->MethodA();
else
return false //轉換出錯,輸入參數不是我們期望的類型;
b->MethodB(); //調用介面B中的方法;
…
return true;
}
可以看到,軟體模組依賴於一個介面B,同時還要驗證其是否繼承自A,否則便不是該模組所需的類型。
該模組可以很好地運行。但注意上面對類型A的轉換是通過動態類型轉換來實現的。而其具體的實現依賴於RTTI,而在某些情況下,我們不能應用RTTI機制。如果我們關掉編譯器的RTTI選項。上面的代碼可能就不能正確運行了。怎麼辦呢?如果我們知道多重繼承的機制,這或許可以協助我們解決這個問題。可以參考C++編程思想或深度探索C++物件模型的相關章節。
下面是一種解決方案。
我們在B中增加一個多態的virtual void* GetThis()方法用以返回this指標,並在A11,A22,A33中都有它的實現,在應用環境中:
bool App::AddSub(B* b)
{
A* a = static_cast<A*>(b->GetThis());
a->MethodA(); //調用介面A中的方法;
b->MethodB(); //調用介面B中的方法;
…
return true;
}
上面的實現中通過多態的GetThis()方法,獲得具體實作類別的指標並轉換為A,但是由於編譯器關閉了對RTTI的支援,這種轉換並非型別安全,需要事先約定傳入類型包括了對A,B的實現,否則轉換將不能達到我們期望的效果。