天天看點

【c++】虛函數描述符override

在C++11中為了幫助程式員寫繼承結構複雜的類型,引入了虛函數描述符override,如果派生類在虛函數聲明時使用了override描述符,那麼該函數必須重載其基類中的同名函數,否則代碼将無法通過編譯。我們來看一下如代碼清單2-25所示的這個簡單的例子。

代碼清單2-25

struct Base {

    virtual void Turing() = 0;

    virtual void Dijkstra() = 0;

    virtual void VNeumann(int g) = 0;

    virtual void DKnuth() const;

    void Print();

};

struct DerivedMid: public Base {

    // void VNeumann(double g);

    // 接口被隔離了,曾想多一個版本的VNeumann函數

struct DerivedTop : public DerivedMid {

    void Turing() override;

    void Dikjstra() override;           // 無法通過編譯,拼寫錯誤,并非重載

    void VNeumann(double g) override;   // 無法通過編譯,參數不一緻,并非重載

    void DKnuth() override;             // 無法通過編譯,常量性不一緻,并非重載

    void Print() override;              // 無法通過編譯,非虛函數重載

// 編譯選項:g++ -c -std=c++11 2-10-3.cpp

在代碼清單2-25中,我們在基類Base中定義了一些virtual的函數(接口)以及一個非virtual的函數Print。其派生類DerivedMid中,基類的Base的接口都沒有重載,不過通過注釋可以發現,DerivedMid的作者曾經想要重載出一個“void VNeumann(double g)”的版本。這行注釋顯然迷惑了編寫DerivedTop的程式員,是以DerivedTop的作者在重載所有Base類的接口的時候,犯下了3種不同的錯誤:

函數名拼寫錯,Dijkstra誤寫作了Dikjstra。

函數原型不比對,VNeumann函數的參數類型誤做了double類型,而DKnuth的常量性在派生類中被取消了。

重寫了非虛函數Print。

如果沒有override修飾符,在代碼清單2-25中,DerivedTop作者的4處可以編譯過去 但是與他的願意(想重載虛函數)有嚴重的偏差了 但是編譯器不報錯,繼續編譯下去 這樣就難排查了。加上關鍵字override

這樣編譯器可以輔助檢查是不是正确重載 。

如果沒有override修飾符 DerivedTop的作者可能在編譯後都沒有意識到自己犯了這麼多錯誤。因為編譯器對以上3種錯誤不會有任何的警示。這裡override修飾符則可以保證編譯器輔助地做一些檢查。我們可以看到,在代碼清單2-25中,DerivedTop作者的4處錯誤都無法通過編譯。

此外,值得指出的是,在C++中,如果一個派生類的編寫者自認為新寫了一個接口,而實際上卻重載了一個底層的接口(一些簡單的名字如get、set、print就容易出現這樣的狀況),出現這種情況編譯器還是愛莫能助的。不過這樣無意中的重載一般不會帶來太大的問題,因為派生類的變量如果調用了該接口,除了可能存在的一些虛函數開銷外,仍然會執行派生類的版本。是以編譯器也就沒有必要提供檢查“非重載”的狀況。而檢查“一定重載”的override關鍵字,對程式員的實際應用則會更有意義。

還有值得注意的是,如我們在第1章中提到的,final/override也可以定義為正常變量名,隻有在其出現在函數後時才是能夠控制繼承/派生的關鍵字。通過這樣的設計,很多含有final/override變量或者函數名的C++98代碼就能夠被C++編譯器編譯通過了。但出于安全考慮,建議讀者在C++11代碼中應該盡可能地避免這樣的變量名稱或将其定義在宏中,以防發生不必要的錯誤。

建議:如果派生類裡面是像重載虛函數 就加上關鍵字override 這樣編譯器可以輔助檢查是不是正确重載,如果沒加這個關鍵字 也沒什麼嚴重的error 隻是少了編譯器檢查的安全性