M33 將非尾端類設計為抽象類

2021-09-08 14:20:34 字數 1842 閱讀 2914

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、結論,對於繼承體系中的非尾端類,應該設計為抽象類,如果使用外界的程式庫,需要做一下變通。