在閱讀C++項目(caffe)源碼時,發現不少基類不僅把常規的成員函數定義成虛函數(virtual),也會把析構函數定義為虛函數,稍稍思考下,這樣做的確是有原因的,本文將結合C++代碼實例嘗試探討下。
常規
隨便寫一段C++代碼作為實例,在這個例子中,我們先不把析構函數定義為虛函數:
這段代碼的邏輯很簡單,無非就是定義了兩個類:類Base的成員函數foo()為虛函數,構造函數和析構函數都是常規函數,此外它還有個public的成員變量buf。類Child則公開繼承了Base,因此它可以直接使用Base::buf——在構造函數中new了一段內存,并且在析構函數delete掉它。
Childc;
c.foo();
我們直接使用Child實例化一個對象c,調用c.foo(),此時得到如下輸出:
一切盡在預料中。
不安全的問題
雖說對象c調用foo()的輸出完全符合預計,但像上面那樣定義類仍然是非常危險的做法。在這一節我們曾討論過,父類指針可以調用派生類的重寫函數,因此下面這兩行C++代碼也是合法的,請看:
編譯這段C++代碼完全沒有問題,運行也不會報錯,輸出如下:
Baseconstruct
Childconstruct
Child::foo
Basedeconstruct
可是,從輸出信息能夠看出,派生類Child的析構函數沒有被調用,對于本例而言,new出來的buf沒有對應的delete,勢必會造成內存泄漏。
解決問題
要解決所謂的“不安全問題”,其實很簡單,按照題目說的做——將基類的析構函數也定義為虛函數就可以了,請看修改后的C++代碼:
也即盡在基類Base的析構函數前加上virtual關鍵字,其他的所有代碼都無需改動。現在再執行下面的這幾行C++代碼:
輸出如下:
顯然,此時派生類Child的析構函數也會被調用了,內存泄漏的問題倍解決了。
小結
C++中的virtual關鍵字是非常好用,也是C++程序員必須掌握的關鍵字,其實,“不安全問題”出現的原因也是簡單的:我們在靜態類型與動態綁定一節中提到過,基本上只有涉及到virtual函數時,才會發生動態綁定,此時通過對象指針(pb)調用的函數由它指向的類(Child)決定,所以此時派生類Child的析構函數會被調用。如果基類Base的析構函數不是虛函數,那么對象指針(pb)調用的函數由其靜態類型(Base)決定,也即調用的其實只是基類Base的析構函數而已。