回覆列表
-
1 # IT劉小虎
-
2 # 外老師
面試中經常考察的一個知識點。涉及到類內部資源釋放,子類基類的解構函式呼叫順序等知識點。
子類基類解構函式呼叫順序當某個類D,派生於某個基類B的時候,釋放類D的某個物件的時候,不僅僅會呼叫D的解構函式,還會自動呼叫B的解構函式。
當然,呼叫的順序是先D後B,而且在執行D的解構函式的時候,D的物件,還是相對完整的一個狀態,這時候,虛擬函式表還在正常工作的,也就是還可以呼叫虛擬函式。
子類資源釋放如果說透過B宣告一個指標或者引用b,指向一個D的物件。那麼透過b來釋放資源的時候,也能夠做到先呼叫D的解構函式,再呼叫B的解構函式的效果。
從而保證D和B兩個類中涉及到的資源,都可以正常釋放。
總結所以說,虛解構函式的目的,只要是保證子類和基類的資源可以正常釋放。
當然,如果某個類的解構函式,如果不是虛擬函式,那麼在C++裡面,就相當於告訴開發者,這個類不能派生子類。否則很容易出現資源未正常釋放的問題。
在閱讀C++專案(caffe)原始碼時,發現不少基類不僅把常規的成員函式定義成虛擬函式(virtual),也會把解構函式定義為虛擬函式,稍稍思考下,這樣做的確是有原因的,本文將結合C++程式碼例項嘗試探討下。
常規隨便寫一段C++程式碼作為例項,在這個例子中,我們先不把解構函式定義為虛擬函式:
這段程式碼的邏輯很簡單,無非就是定義了兩個類:類 Base 的成員函式 foo() 為虛擬函式,建構函式和解構函式都是常規函式,此外它還有個 public 的成員變數 buf。類 Child 則公開繼承了 Base,因此它可以直接使用 Base::buf——在建構函式中 new了一段記憶體,並且在解構函式 delete 掉它。
Child c;
c.foo();
我們直接使用 Child 例項化一個物件 c,呼叫 c.foo(),此時得到如下輸出:
一切盡在預料中。
不安全的問題雖說物件 c 呼叫 foo() 的輸出完全符合預計,但像上面那樣定義類仍然是非常危險的做法。在這一節我們曾討論過,父類指標可以呼叫派生類的重寫函式,因此下面這兩行C++程式碼也是合法的,請看:
編譯這段C++程式碼完全沒有問題,執行也不會報錯,輸出如下:
Base construct
Child construct
Child::foo
Base deconstruct
可是,從輸出資訊能夠看出,派生類 Child 的解構函式沒有被呼叫,對於本例而言,new 出來的 buf 沒有對應的 delete,勢必會造成記憶體洩漏。
解決問題要解決所謂的“不安全問題”,其實很簡單,按照題目說的做——將基類的解構函式也定義為虛擬函式就可以了,請看修改後的C++程式碼:
也即盡在基類 Base 的解構函式前加上 virtual 關鍵字,其他的所有程式碼都無需改動。現在再執行下面的這幾行C++程式碼:
輸出如下:
顯然,此時派生類 Child 的解構函式也會被呼叫了,記憶體洩漏的問題倍解決了。
小結C++ 中的 virtual 關鍵字是非常好用,也是C++程式設計師必須掌握的關鍵字,其實,“不安全問題”出現的原因也是簡單的:我們在靜態型別與動態繫結一節中提到過,基本上只有涉及到 virtual 函式時,才會發生動態繫結,此時透過物件指標(pb)呼叫的函式由它指向的類(Child)決定,所以此時派生類 Child 的解構函式會被呼叫。如果基類 Base 的解構函式不是虛擬函式,那麼物件指標(pb)呼叫的函式由其靜態型別(Base)決定,也即呼叫的其實只是基類 Base 的解構函式而已。