Guru of the Week 條款05:覆寫虛擬函式 (轉)
GotW #05 Overriding Virtual Functions
著者:Herb Sutter
翻譯:kingofark
[宣告]:本文內容取自網站上的Guru of the Week欄目,其著作權歸原著者本人所有。譯者kingofark在未經原著者本人同意的情況下翻譯本文。本翻譯內容僅供自學和參考用,請所有閱讀過本文的人不要擅自轉載、傳播本翻譯內容;本翻譯內容的人請在閱讀瀏覽後,立即刪除其。譯者kingofark對違反上述兩條原則的人不負任何責任。特此宣告。
Revision 1.0
Guru of the Week 條款05:覆寫虛擬:namespace prefix = o ns = "urn:schemas--com::office" />
難度:6 / 10
(虛擬函式(virtual function)真是一個招人喜歡的基本特性,對嗎?好吧,如果你能回答下面這個問題,你就會發現她們有時真是冷若冰霜、寒氣刺骨。)
[問題]
當你在一個佈滿灰塵的角落翻尋公司的存檔程式碼時,你偶然中發現了無名氏編寫的一段如下的。這位無名氏程式設計師好像曾經試圖用這個程式做試驗,看看某些C++特性的運作狀況。你知道這個程式設計師希望程式執行後列印什麼結果嗎?你知道程式執行後的實際結果嗎?
#include #include using namespace std; class Base { public: virtual void f( int ) { cout << "Base::f(int)" << endl; } virtual void f( double ) { cout << "Base::f(double)" << endl; } virtual void g( int i = 10 ) { cout << i << endl; } }; class Derived: public Base { public: void f( complex cout << "Derived::f(complex)" << endl; } void g( int i = 20 ) { cout << "Derived::g() " << i << endl; } }; void main() { Base b; Derived d; Base* pb = new Derived; b.f(1.0); d.f(1.0); pb->f(1.0); b.g(); d.g(); pb->g(); delete pb; } [解答] 首先,我們談談編碼風格方面的問題: 1. void main() 這並不是一個合法的main宣告,儘管許多都允許這種寫法。應該使用“int main()”或者“int main(int argc, char* argv[])”。 然而,其實你仍然不需要新增任何返回語句(雖然有時加上返回語句是為了形成一種向外部者報告錯誤的良好編碼風格)。實際上,如果main沒有返回語句,其效果等同於了“return 0;”。 2. delete pb; 這看起來好像既無公害又無傷大雅——當然,前提是Base的編寫者提供了一個虛擬解構函式(virtual destructor)。然而,就像我們在這個程式中所看到的那樣,在沒有虛擬解構函式(virtual destructor)的情況下透過指向基類的指標進行刪除操作,這簡直就是邪惡的犯罪,同時也是幼稚和簡單的——如此以來,崩潰就是你所能期待的最好的事情了。 [規則]:把基類的解構函式(virtual destructor)宣告為virtual。 3. Derived::f(complex Derived並沒有過載(overload)Base::f,而是隱藏了它。這個細節非常重要,因為這意味著在Derived中,Base::f(int)和Base::f(double)是不可見的!(更何況某些流行的編譯器對此種情況甚至都不給出一個警告資訊。) [規則]:當派生類中的函式與基類中的函式同名,而你又不想在派生類中隱藏這些基類函式的時候,請使用using宣告來把它們定置到可用範圍(pe)之內。 4. Derived::g(int i = 10) 除非你是故意要把別人搞糊塗,否則不要改變你所覆寫(overr)的繼承函式(inherited function)的預設引數。(一般來說,進行覆寫而不使用引數預設值並不是一個壞主意,但那是其本身的問題。)是的,這是一個合法合理的C++語句;不錯,其結果也被很好的定義了;但是,不,請不要這樣做。往下接著看,你會發現它到底是如何把人搞糊塗的。 [規則]:絕不要改變覆寫的繼承函式(overridden inherited function) 好了,讓我們不要再談那些編碼風格的瑣事了,現在我們就來看看主程式到底是不是按照那位無名氏程式設計師所期望的方式運作的。 void main() { Base b; Derived d; Base* pb = new Derived; * b.f(1.0); 沒問題,呼叫Base::f(double)。 * d.f(1.0); 這條語句呼叫Derived::f(complex 無名氏先生也許原本是想要呼叫Base::f(double),但是此處他連一個編譯錯誤資訊也得不到,因為幸運的是(?),complex (*)注:在現有的C++標準草案中,轉換建構函式(conversion constructor)不是顯式的(explicit)。 * pb->f(1.0); 有趣的事情在這裡發生了:雖然Base* pb指向一個Derived,但這個語句還是呼叫Base::f(double),因為過載解析過程(overload resolution)會以靜態型別(在這裡是指Base)完成,而不是以動態型別(在這裡是指Derived)完成。 * b.g(); 這條語句只是簡單的列印“10”,因為它引起對Base::g(int)的呼叫,其函式引數預設值為10。這沒什麼好奇怪的。 * d.g(); 這條語句列印“Derived::g()20”,因為它引起對Derived::g(int)的呼叫,其函式引數預設值為20。這也沒什麼好奇怪的。 * pb->g(); 這條語句列印“Derived::g()10”。這個結果也許會暫時死鎖你的精神剎車,導致你聲嘶力竭的進入一種精神停滯狀態——直到你認識到,編譯器的所作所為是再正常不過的了(雖然我們可憐的無名氏毫無疑問的應該被槍斃)。要記住,確定預設引數和過載一樣,是以物件的靜態型別(在這裡是指Base)完成的,因此選用了預設值10。然而,函式又恰好是virtual的,所以實際被呼叫的函式取決於物件的動態型別(在這裡是指Derived)。 最後,如果你能理解這剩下的幾個語句(儘管你會因此說:“噢嘔!”),那麼你就終於可以理解我開頭所說的“冷若冰霜、寒氣刺骨”的意思了。祝賀你! * delete pb; } 刪除操作,當然,會留下一些只被部分清除的東西,使變得不可捉摸……好好看看前面關於虛擬解構函式(virtual destructor)的敘述吧。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-990890/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 條款05: 瞭解c++默默編寫並呼叫哪些函式C++函式
- 虛擬函式,虛擬函式表函式
- 虛擬函式 純虛擬函式函式
- 介面、虛擬函式、純虛擬函式、抽象類函式抽象
- [Lang] 虛擬函式函式
- 【C++筆記】虛擬函式(從虛擬函式表來解析)C++筆記函式
- 【C++筆記】虛擬函式(從虛擬函式概念來解析)C++筆記函式
- Oracle OCP(05):轉換函式Oracle函式
- c++虛擬函式表C++函式
- 虛擬函式與多型函式多型
- 虛擬函式的呼叫原理函式
- 05 函式函式
- 虛擬函式的實現原理函式
- C++ 介面(純虛擬函式)C++函式
- C++ 虛擬函式表解析C++函式
- 深入C++成員函式及虛擬函式表C++函式
- C++多型之虛擬函式C++多型函式
- 抽象基類和純虛擬函式抽象函式
- 內聯(inline)函式與虛擬函式(virtual)的討論inline函式
- MySQL內建函式:year()、 week()相容MySql函式
- C++虛擬函式學習總結C++函式
- C++之類解構函式為什麼是虛擬函式C++函式
- 基類指標、虛純虛擬函式、多型性、虛析構指標函式多型
- C++建構函式和解構函式呼叫虛擬函式時使用靜態聯編C++函式
- 虛擬函式的記憶體佈局(上)函式記憶體
- 關於虛擬函式的一些理解函式
- C++ 派生類函式過載與虛擬函式繼承詳解C++函式繼承
- Java常見知識點彙總(④)——虛擬函式、抽象函式、抽象類、介面Java函式抽象
- C++純虛擬函式簡介及區別C++函式
- c++虛擬函式實現計算表示式子C++函式
- 條件函式函式
- C++物件導向總結——虛指標與虛擬函式表C++物件指標函式
- 05_jQuery函式的使用jQuery函式
- hive05_視窗函式Hive函式
- MySQL函式-條件判斷函式MySql函式
- setV:一個管理 Python 虛擬環境的 Bash 函式Python函式
- 詳解C++中的多型和虛擬函式C++多型函式
- 虛擬函式表-C++多型的實現原理函式C++多型
- impala 條件函式函式