1、考慮下面的需求,軟體處理動物,cat與dog需要特殊處理,因此,設計cat和dog繼承animal。animal有copy賦值(不是虛方法),cat和dog也有copy賦值。考慮下面的情況:
cat cat1;
cat cat2;
animal *a1 = &cat1;
animal *a2 = &cat2;
*a1 = *a2;
思考*a1 = *a2會有什麼問題? copy賦值不是虛方法,根據表面型別,呼叫animal的copy賦值,這就導致所謂的部分賦值,cat2的animal成分賦值給cat1的animal成分,二者的cat成分保持不變。
2、怎麼解決上面的問題?
將animal的copy賦值宣告為virtual方法,如下:
virtual animal& operator=(const animal &rhs);
cat和dog重寫:
virtual cat& operator=(const animal &rhs);
virtual dog& operator=(const animal &rhs);
這裡使用了c++語言後期的乙個特性,即協變,返回的引用更加具體。但是,對於形參表,重寫必須保證保持一致。將copy賦值宣告為virtual,解決了部分賦值的問題。但是,引入了乙個新的問題。如下:
cat cat;
dog dog;
animal* a1 = &cat;
animal* a2 = &dog;
*a1 = *a2;
這是異型賦值,左邊是cat,右邊是dog。c++是強型別語言,一般情況下,異型賦值不合法,不會造成問題。但是,這種情況下導致異型賦值合法。對於指標解引用的情況,我們期望同型賦值是合法的,異型賦值是非法的。容易想到的辦法是,在重寫的copy賦值中,使用dynamic_cast進行同型判斷。比如cat的copy賦值,首先判斷rhs是不是cat,如果是,就賦值,如果不是,丟擲異常。
我們知道,使用dynamic_cast效率低,考慮下面的情況,cat1 = cat2; 即使cat1與cat2的表面型別就是cat,也會呼叫cat& operator=(const animal &rhs),進行一次dynamic_cast的運算,這不是我們所期望的。解決辦法是:增加乙個過載方法,編譯器編譯時,根據表面型別確定方法的呼叫。如下:cat& operator=(const cat &rhs)。同時對於重寫的方法,可以呼叫前面的方法,如下:
cat& operator=(const animal &rhs)
3、執行期的型別檢查,dynamic_cast的使用應該盡量避免。因為,首先效率低,其次,有些編譯器還不支援dynamic_cast,不具有移植性。有沒有更好的辦法?
導致問題的原因是,對於指標解引用的賦值,父類的copy賦值不是虛方法,導致部分賦值。
因此,解決辦法是,提取乙個抽象類abstractanimal,將copy賦值宣告為protected,子類可以呼叫,表面型別是抽象類的指標解引用賦值,不能呼叫。增加乙個animal類,繼承abstractanimal。
對於抽象類,內部至少要有乙個純虛方法,很自然地將析構方法宣告為純虛方法。對於純虛方法,需要注意:
a、純虛方法意味著當前類為抽象類,不能例項化。
b、純虛方法要求子類必須重寫。
c、特別注意,純虛方法一般不提供實現,但是允許提供實現,子類也可以呼叫。如果析構方法為純虛方法,必須要提供實現。因為子類呼叫自身的析構方法後,必定會去呼叫父類的析構方法。
4、考慮,具體基類沒有字段,是不是就不需要上述的抽象類了?這有兩個問題,首先現在沒有字段,以後可能會有字段,其次如果乙個類沒有字段,一開始就應該是乙個抽象類。
5、結論,對於繼承體系中的非尾端類,應該設計為抽象類,如果使用外界的程式庫,需要做一下變通。