1. 程式人生 > >深度剖析C++解構函式

深度剖析C++解構函式

建構函式和解構函式在C中意味著生命週期的開始和結束,它們的實現原理相同。由於解構函式往往還設定成虛擬函式,所以這裡我重點介紹下C解構函式的原理和各種場景。
一、解構函式的作用
當物件的生命週期結束時,會自動呼叫解構函式,以清理一些資源,比如釋放記憶體、關閉檔案、關閉資料庫連線等等。
二、解構函式呼叫的時機
(1)基類析構
4624d87b3bb64c79bff4bf5a7924c345.png
我們反彙編下檢視上面程式碼:
526d15332d5b4763becae057a465c347.png
從反彙編中可以看出,在物件離開它的作用域時,編譯器自動給我們添加了一個解構函式呼叫的語句。
那我們使用new產生的物件會什麼時候呼叫解構函式呢,這裡我們把fun1裡物件改成動態生成。
void fun1()
{
Base *base = new Base();

cout<<“fun1 over”<<endl;
}
當我們不使用delete釋放記憶體時,看反彙編的情況
8e129ee60b03423091ba459db2bacf52.png
此時,沒有任何地方呼叫Base的解構函式
當我們使用delete釋放物件時,
void fun1()
{
Base *base = new Base();

delete base;
cout<<“fun1 over”<<endl;
}
我們反彙編結果:
be6c9b24aa034331b5413629ea13b107.png
這裡我們看到解構函式呼叫了,這是因為當我們使用delete刪除物件時,編譯器會自動在後面新增一條呼叫解構函式的語句;
從這裡我們也可以看書,C中的new和delete和c語言中malloc和free的唯一區別就是使用new和delete編譯會自動新增一條呼叫構造
函式和解構函式的語句,其他作用一樣,都是釋放記憶體,所以我們在C

中一定要是否new和delete操作記憶體,不然就沒辦法呼叫構造或
析構函數了
(2)派生類析構
前面分析的是基類的解構函式,那派生類物件析構時自身和基類的解構函式什麼時候呼叫呢,這裡我們新增一個派生類:
35acbecef8cf464f9f0f298c245cc40c.png
從上面的分析我們可以知道,編譯器會給我們新增一個派生類的解構函式呼叫,我們分析下派生類的解構函式
68239ef1f5bf4c9684c1698955ac708f.png
我們可以看出,派生類解構函式執行完後會執行基類的建構函式,這說明編譯器給派生類解構函式後面都會增加一個直接父類的解構函式呼叫的語句,
這樣就能保證派生類的所有父類的解構函式都會呼叫。
三、為什麼要將基類的解構函式設定為虛擬函式
這裡我們將派生類的物件賦值給基類的引用
e36d199a1d1645d3a9440b66e5194c0f.png
這時候我們反彙編看解構函式的呼叫情況:
25d812bc5e024a09b7d5464a00d5a725.png

這裡我們看到,編譯器只給我添加了基類的解構函式呼叫,我們從上面分析指導,只有派生類的解構函式後面會新增父類的解構函式呼叫,而父類的解構函式
後面沒有子類的解構函式呼叫,這就造成了派生類的解構函式沒有呼叫,導致一些資源沒有正常釋放。
當我們把基類的解構函式設定為虛擬函式時,這時我們反彙編:
a28bd90edece4c5fa0f74308d0bb64d0.png
這個時候我們發現,編譯器給我們新增呼叫的就不是一個具體的析構函數了,那我們呼叫的到底是哪個解構函式呢?
原來當我們將解構函式定義為虛擬函式的時候,編譯器就會為每個物件新增一個虛表,儲存著該物件例項類的虛擬函式指標(這裡Base物件例項儲存的就是Base類對應的解構函式指標,
Child物件例項儲存的就是Child類的解構函式指標)。這時編譯器就不會靜態為我們生成呼叫那個具體的解構函式,而時在執行的時候查詢這種虛表,找到對應的呼叫地址,因為
我們這裡是Child物件例項,因此呼叫的就是Child類的解構函式,從而保證派生類和父類的解構函式都會呼叫。
四、什麼時候要將解構函式設為虛擬函式
將所有類設為虛擬函式好嗎?從上面可以看出,設為虛擬函式後,類的物件例項會增加一個虛表,佔用額外的記憶體空間,而且呼叫的時候會查表再呼叫,對程式的效能影響不好;所以,

在真實的開發環境中,只需將那些實現多型的類(將子類物件指向父類引用)的基類設為虛擬函式就行了,這樣可以避免程式效能變差。

原文連結:http://www.uptoday.net/articles/2017/12/09/1512825909082.html