C++中虛析構函數的作用(1)(轉)

★★什麼時候要用虛析構函數★★

通過基類的指針來刪除派生類的對象時,基類的析構函數應該是虛的。否則其刪除效果將無法實現。

一般情況下,這樣的刪除只能夠刪除基類對象,而不能刪除子類對象,形成了刪除一半形象,從而千萬內存泄漏。

原因:

在公有繼承中,基類對派生類及其對象的操作,只能影響到那些從基類繼承下來的成員。如果想要用基類對非繼承成員進行操作,則要把基類的這個操作(函數)定義爲虛函數。
那麼,析構函數自然也應該如此:如果它想析構子類中的重新定義或新的成員及對象,當然也應該聲明爲虛的。

注意:

如果不需要基類對派生類及對象進行操作,則不能定義虛函數(包括虛析構函數),因爲這樣會增加內存開銷。

語法如下:

class Base
 
public:
      Base( ) 
{  };
  
virtual    ~Base( ) {  };   //注意:基類的析構函數被定義爲虛的
 }
;

class Derived
 
{
  
public:
      Derived( ) 
{  };
    
~Derived( ) {  }
 }
;

void main( )
 
{
  Base 
*p;
  p 
= new Derived;
  delete p;
 }

注:似乎這樣的使用經常在new和delete行爲時。

=================================================

我們知道,用C++開發的時候,用來做基類的類的析構函數一般都是虛函數。可是,爲什麼要這樣做呢?下面用一個小例子來說明:    
    有下面的兩個類:

class ClxBase
{
public:
    ClxBase() {};
    
virtual ~ClxBase() {};

    
virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};

class ClxDerived : public ClxBase
{
public:
    ClxDerived() {};
    
~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; }; 

    
void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};

    代碼

ClxBase *pTest = new ClxDerived;
pTest
->DoSomething();
delete pTest;

    的輸出結果是:

Do something in class ClxDerived!
Output from the destructor of class ClxDerived!

    這個很簡單,非常好理解。
    但是,如果把類ClxBase析構函數前的virtual去掉,那輸出結果就是下面的樣子了:

Do something in class ClxDerived!

    也就是說,類ClxDerived的析構函數根本沒有被調用!一般情況下類的析構函數裏面都是釋放內存資源,而析構函數不被調用的話就會造成內存泄漏。我想所有的C++程序員都知道這樣的危險性。當然,如果在析構函數中做了其他工作的話,那你的所有努力也都是白費力氣。
    所以,文章開頭的那個問題的答案就是--這樣做是爲了當用一個基類的指針刪除一個派生類的對象時,派生類的析構函數會被調用。
    當然,並不是要把所有類的析構函數都寫成虛函數。因爲當類裏面有虛函數的時候,編譯器會給類添加一個虛函數表,裏面來存放虛函數指針,這樣就會增加類的存儲空間。所以,只有當一個類被用來作爲基類的時候,才把析構函數寫成虛函數。

寫成虛的是爲了在實現多態的時候不造成內存泄露, 比如:

class a
{
int aa;
public:
virtual ~a(){};
};

class b : public a
{
int bb;
};

如果你這樣:

a *pa = new b; // upcast

然後這樣:

delete pa;

這句delete, 如果你基類的析構函數不是虛的的話, 就會造成內存泄露, 具體表現爲派生類的內存被釋放了而基類沒有.(疑問:似乎應該是基類內存釋放而派生類內存沒有釋放)


我已經給你了參考資料的地方, Efftive C++里人家說的已經很好了, 我表達能力又不好, 在繼承中使用多態來創建動態對象時, 比如上面的:a *pa = new b;
由於pa是個基類的指針, 只能識別屬於基類的部分, 所以如果沒有虛析構函數的話, 那麼子類中特有的部分就不會被釋放, 造成"經典"的釋放一半, 泄露一半的內存泄露.
這和object slicing的原理是一樣的, 至於object slicing:

#include <iostream>
#include <string>
using namespace std;

class Pet
{
public:
Pet(const string& _category_) : category(_category_){}

virtual void Desc()
{
cout << "This is a " << category << "./n";
}

virtual const string& GetCate()
{
return category;
}

virtual ~Pet(){}

private:
string category;
};


class Cat : public Pet
{
public:
Cat(const string& _category_, const string& _name_)
: Pet(_category_), name(_name_){}

virtual void Desc()
{
cout << "This is a " << Pet::GetCate() << "./n";
cout << "Its name is " << name << endl;
}

private:
string name;
};


void Describe(Pet p) // object slicing
{
p.Desc();
}

int main()
{
Pet p("Yellow dog");
Cat c("Black and white cat", "Kitty");
Describe(p);
Describe(c); // object slicing
}
所以表現在動態對象上就會造成delete不完全, 造成內存泄露.

我的編譯器警告級別被我調成最高, 有一次寫類多態的時候它就警告我base類中沒有虛的虛構函數, 我開始也不懂爲什麼, 但既然警告了就說明一定有問題, 後來查了資料就知道了, 自己也長了見識. 一般的, 只要一個類要作爲其他類的基類, 那麼它就一定有虛函數, 只要一個類中有虛函數, 那麼它的析構函數就一定也要是虛的, 否則就會造成我以上所說的問題, 你以後自己多看點書查查資料吧...


★★要點★★  爲什麼繼承一個沒有虛析構函數的類是危險的?

  一個沒有虛析構函數的類意味着不能做爲一個基類。如std::string, 
std::complex, 和 std::vector 都是這樣的。爲什麼繼承一個沒有虛析構函數的
類是危險的?當你公有繼承創建一個從基類繼承的相關類時,指向新類對象中的指
針和引用實際上都指向了起源的對象。因爲析構函數不是虛函數,所以當你delete
一個這樣的類時,C++就不會調用析構函數鏈。舉個例子說明:
 
class A
{
 public:
 ~A() // 不是虛函數
 {
 // ...
 }
}; 
class B: public A //錯; A沒有虛析構函數
{
 public:
 ~B()
 {
 // ...
 }
};

int main()
{
 A * p = new B; //看上去是對的
 delete p; //錯,B的析構函沒有被調用

 源自:http://blog.chinaunix.net/u/10219/showart.php?id=355724

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章