from:https://blog.csdn.net/iicy266/article/details/11906457
知識背景
? ? ? ? ?要弄明白這個問題,首先要了解下C++中的動態綁定。?
? ? ? ? ?關于動態綁定的講解,請參閱: ?C++中的動態類型與動態綁定、虛函數、多態實現
正題
? ? ? ? ?直接的講,C++中基類采用virtual虛析構函數是為了防止內存泄漏。具體地說,如果派生類中申請了內存空間,并在其析構函數中對這些內存空間進行釋放。假設基類中采用的是非虛析構函數,當刪除基類指針指向的派生類對象時就不會觸發動態綁定,因而只會調用基類的析構函數,而不會調用派生類的析構函數。那么在這種情況下,派生類中申請的空間就得不到釋放從而產生內存泄漏。所以,為了防止這種情況的發生,C++中基類的析構函數應采用virtual虛析構函數。
示例代碼講解
現有Base基類,其析構函數為非虛析構函數。Derived1和Derived2為Base的派生類,這兩個派生類中均有以string* 指向存儲其name的地址空間,name對象是通過new創建在堆上的對象,因此在析構時,需要顯式調用delete刪除指針歸還內存,否則就會造成內存泄漏。
- class?Base?
- {??
- ? ?public:??
- ? ? ~Base()?{??
- ? ? ? cout?<<?"~Base()"?<<?endl;??
- ? ? ?}??
- };??
- class?Derived1?:?public?Base?{??
- ?public:??
- ??Derived1():name_(new?string("NULL"))?{} ?//通過new創建在堆上的對象
- ??Derived1(const?string&?n):name_(new?string(n))?{}??
- ??
- ??~Derived1()?{??
- ????delete?name_;??
- ????cout?<<?"~Derived1():?name_?has?been?deleted."?<<?endl;??
- ??}??
- ??
- ?private:??
- ??string*?name_;??
- };??
- ??
- class?Derived2?:?public?Base?{??
- ?public:??
- ??Derived2():name_(new?string("NULL"))?{}??
- ??Derived2(const?string&?n):name_(new?string(n))?{}??
- ??
- ??~Derived2()?{??
- ????delete?name_;??
- ????cout?<<?"~Derived2():?name_?has?been?deleted."?<<?endl;??
- ??}??
- ??
- ?private:??
- ??string*?name_;??
- };??
- int?main()?{??
- ??Derived1*?d1?=?new?Derived1(); //d1為Derived1類的指針,它指向一個在堆上創建的Derived1的對象,需要delete調用?
- ??Derived2?d2?=?Derived2("Bob"); ?//d2為一個在棧上創建的對象,執行結束后,自動調用析構
- ??delete?d1;??
- ??return?0;??
- }??
剛才我們說,Base基類的析構函數并不是虛析構函數,現在結果顯示,派生類的析構函數被調用了,正常的釋放了其申請的內存資源。這兩者并不矛盾,因為無論是d1還是d2,兩者都屬于靜態綁定,而且其靜態類型恰好都是派生類,因此,在析構的時候,即使基類的析構函數為非虛析構函數,也會調用相應派生類的析構函數。
下面我們來看下,當發生動態綁定時,也就是當用基類指針指向派生類,這時候采用delete顯式刪除指針所指對象時,如果Base基類的析構函數沒有virtual,會發生什么情況?
- int?main()?{??
- ??Base*?base[2]?=?{ ?new?Derived1(), new?Derived2("Bob") }; ?
- ??for?(int?i?=?0;?i?!=?2;?++i)?{??
- ????delete?base[i];??????
- ??}??
- ??return?0;??
- }??

? ? ? ? 從上面結果我們看到,盡管派生類中定義了析構函數來釋放其申請的資源,但是并沒有得到調用。原因是基類指針指向了派生類對象,而基類中的析構函數卻是非virtual的,之前講過,虛函數是動態綁定的基礎。現在析構函數不是virtual的,因此不會發生動態綁定,而是靜態綁定,指針的靜態類型為基類指針,因此在delete時候只會調用基類的析構函數,而不會調用派生類的析構函數。這樣,在派生類中申請的資源就不會得到釋放,就會造成內存泄漏,這是相當危險的:如果系統中有大量的派生類對象被這樣創建和銷毀,就會有內存不斷的泄漏,久而久之,系統就會因為缺少內存而崩潰。
? ? ? ? 也就是說,在基類的析構函數為非虛析構函數的時候,并不一定會造成內存泄漏;當派生類對象的析構函數中有內存需要收回,并且在編程過程中采用了基類指針指向派生類對象,如為了實現多態,并且通過基類指針將該對象銷毀,這時,就會因為基類的析構函數為非虛析構函數而不觸發動態綁定,從而沒有調用派生類的析構函數而導致內存泄漏。
? ? ? ? 因此,為了防止這種情況下內存泄漏的發生,最好將基類的析構函數寫成virtual虛析構函數。
下面把Base基類的析構函數改為虛析構函數:
- class?Base?{??
- ?public:??
- virtual?~Base()?{??
- ??cout?<<?"~Base()"?<<?endl;??
- }??
- };??
這樣就會實現動態綁定,派生類的析構函數就會得到調用,從而避免了內存泄漏。