1. 程式人生 > >基類的析構函數寫成virtual虛析構函數

基類的析構函數寫成virtual虛析構函數

生命周期 amp 空間 src info 實現 virtual 大量 c++

  虛函數作用:動態綁定,實現多態效果。

  場景問題:

    派生類中有資源需要回收,而在編程中采用多態,由基類的指針指向派生類,則在釋放的時候,如果基類的析構函數不是virtual,則派生類的析構函數得不到釋放

  總結:

  C++中基類采用virtual虛析構函數是為了防止內存泄漏。具體地說,如果派生類中申請了內存空間,並在其析構函數中對這些內存空間進行釋放。假設基類中采用的是非虛析構函數,當刪除基類指針指向的派生類對象時就不會觸發動態綁定,因而只會調用基類的析構函數,而不會調用派生類的析構函數。那麽在這種情況下,派生類中申請的空間就得不到釋放從而產生內存泄漏。所以,為了防止這種情況的發生,C++中基類的析構函數應采用virtual虛析構函數。

  如果靜態類型綁定的指針,即指針的靜態類型就是“派生類”本身,則析構的時候回先調用派生累析構,然後調用基類析構。

//基類,析構不是virtual
 class Base
 {  
 public:  
~Base() {  
  cout << "~Base()" << endl;  
}  
};  

//派生類
class Derived1 : public Base {  
 public:  
  Derived1():name_(new string("NULL")) {}  
  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(); Derived2 d2 = Derived2("Bob"); delete d1; return 0; }

d1為Derived1類的指針,它指向一個在堆上創建的Derived1的對象;d2為一個在棧上創建的對象。其中d1所指的對象需要我們顯式的用delete調用其析構函數;d2對象在其生命周期結束時,系統會自動調用其析構函數。看下其運行結果:

技術分享圖片

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虛析構函數

class Base {  
 public:  
virtual ~Base() {  
  cout << "~Base()" << endl;  
}  
}; 

技術分享圖片

這樣就會實現動態綁定,派生類的析構函數就會得到調用,從而避免了內存泄漏。

endl;

基類的析構函數寫成virtual虛析構函數