C++中基類的析構函數爲何要用virtual虛析構函數

知識背景

         要弄明白這個問題,首先要了解下C++中的動態綁定。 編程

         關於動態綁定的講解,請參閱:  C++中的動態類型與動態綁定、虛函數、多態實現函數

正題

         直接的講,C++中基類採用virtual虛析構函數是爲了防止內存泄漏。具體地說,若是派生類中申請了內存空間,並在其析構函數中對這些內存空間進行釋放。假設基類中採用的是非虛析構函數,當刪除基類指針指向的派生類對象時就不會觸發動態綁定,於是只會調用基類的析構函數,而不會調用派生類的析構函數。那麼在這種狀況下,派生類中申請的空間就得不到釋放從而產生內存泄漏。因此,爲了防止這種狀況的發生,C++中基類的析構函數應採用virtual虛析構函數。測試

示例代碼講解

現有Base基類,其析構函數爲非虛析構函數。Derived1和Derived2爲Base的派生類,這兩個派生類中均有以string* 指向存儲其name的地址空間,name對象是經過new建立在堆上的對象,所以在析構時,須要顯式調用delete刪除指針歸還內存,不然就會形成內存泄漏。spa

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_;
};

咱們看下面對其析構狀況進行測試:.net

int main() {
  Derived1* d1 = new Derived1();
  Derived2 d2 = Derived2("Bob");
  delete d1;
  return 0;
}

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

剛纔咱們說,Base基類的析構函數並非虛析構函數,如今結果顯示,派生類的析構函數被調用了,正常的釋放了其申請的內存資源。這二者並不矛盾,由於不管是d1仍是d2,二者都屬於靜態綁定,並且其靜態類型剛好都是派生類,所以,在析構的時候,即便基類的析構函數爲非虛析構函數,也會調用相應派生類的析構函數。code

下面咱們來看下,當發生動態綁定時,也就是當用基類指針指向派生類,這時候採用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時候只會調用基類的析構函數,而不會調用派生類的析構函數。這樣,在派生類中申請的資源就不會獲得釋放,就會形成內存泄漏,這是至關危險的:若是系統中有大量的派生類對象被這樣建立和銷燬,就會有內存不斷的泄漏,長此以往,系統就會由於缺乏內存而崩潰。blog

        也就是說,在基類的析構函數爲非虛析構函數的時候,並不必定會形成內存泄漏;當派生類對象的析構函數中有內存須要收回,而且在編程過程當中採用了基類指針指向派生類對象,如爲了實現多態,而且經過基類指針將該對象銷燬,這時,就會由於基類的析構函數爲非虛析構函數而不觸發動態綁定,從而沒有調用派生類的析構函數而致使內存泄漏。繼承

        所以,爲了防止這種狀況下內存泄漏的發生,最好將基類的析構函數寫成virtual虛析構函數。

下面把Base基類的析構函數改成虛析構函數:

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

再看下其運行結果:


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

 

故: 繼承時,要養成的一個好習慣就是,基類析構函數中,加上virtual。

轉自:http://blog.csdn.net/iicy266/article/details/11906457

相關文章
相關標籤/搜索