1. 程式人生 > >基類解構函式必須為虛擬函式否則會造成記憶體洩漏

基類解構函式必須為虛擬函式否則會造成記憶體洩漏

看看下面程式有什麼錯誤:

複製程式碼
#include <iostream>
using namespace std;

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

class Son:public Father
{
public:
    Son(){};
    ~Son(){};
};

int main()
{
    Father *pfather=new Son;
    delete pfather;
    pfather=NULL;

    return 0;
}
複製程式碼

該程式在VC++6.0執行後並未發生錯誤,何解?

修改上面程式如下:

複製程式碼
#include <iostream>
using namespace std;

class Father
{
public:
    Father(){cout<<"contructor Father!"<<endl;};
    ~Father(){cout<<"destructor Father!"<<endl;};
};

class Son:public Father
{
public:
    Son(){cout<<"contructor Son!"<<endl;};
    ~Son(){cout<<"destructor Son!"<<endl;};
}; int main() { Father *pfather=new Son; delete pfather; pfather=NULL; return 0; }
複製程式碼

執行結果:

contructor Father!
contructor Son!
destructor Father!

       顯然派生類Son並沒有析構,馬上引出了C++繼承中的虛解構函式問題。

(1)基類的的解構函式不是虛擬函式的話,刪除指標時,只有其類的記憶體被釋放,派生類的沒有。這樣就記憶體洩漏了。

(2)解構函式不是虛擬函式的話,直接按指標型別呼叫該型別的解構函式程式碼,因為指標型別是基類,所以直接呼叫基類解構函式程式碼。

(3)問:啥已經delete p了還能給p賦值啊。。。不解,求高人指點??

       答:delete是刪除指標p指向的例項,p指標本身依然存在,delete後將p置為空值是常用做法,空值一般寫成NULL巨集,其實就是0。因為記憶體0位置是不允許訪問的,delete 0操作編譯器可以判斷是錯誤操作不會執行,因此將p置為空值0是很安全的做法。

(4)養成習慣:基類的析構一定virtual。

(5)當基類指標指向派生類的時候,如果解構函式不宣告為虛擬函式,在析構的時候,不會呼叫派生類的解構函式,從而導致記憶體洩露。

(6)子類物件建立時先呼叫父類建構函式然後在呼叫子類建構函式,在清除物件時順序相反,所以delete p只清除了父類,而子類沒有清除。。。

(7)當基類物件的指標或引用呼叫派生類物件時,如果基類的解構函式不是虛解構函式,則通過基類指標或引用對派生類的析構是不徹底的。

後語:

<span style="font-size: 15px;">(1) 對於這個程式,實際上是沒有關係的,delete pfather雖然只調用了Father類的解構函式,但是程式執行完成,退出main函式後,Son類的部分資料也會被自動清除。</span>
<span style="font-size: 15px;">(2)那麼什麼時候才要用虛解構函式呢?通常情況下,程式設計師的經驗是,當類中存在虛擬函式時要把解構函式寫成virtual,因為類中存在虛擬函式,就說明它有想要讓基類指標或引用指向派生類物件的情況,此時如果派生類的建構函式中有用new動態產生的記憶體,那麼在其解構函式中務必要delete這個資料,但是一般的像以上這種程式,這種操作只調用了基類的解構函式,而標記成虛解構函式的話,系統會先呼叫派生類的解構函式,再呼叫基類本身的解構函式。 
(3)一般情況下,在類中有指標成員的時候要寫copy建構函式,賦值操作符過載和解構函式。 </span>

修改後程式:

複製程式碼
#include <iostream>
using namespace std;

class Father
{
public:
    Father(){cout<<"contructor Father!"<<endl;};
    virtual ~Father(){cout<<"destructor Father!"<<endl;};
};

class Son:public Father
{
public:
    Son(){cout<<"contructor Son!"<<endl;};
    ~Son(){cout<<"destructor Son!"<<endl;};
};

int main()
{
    Father *pfather=new Son;
    delete pfather;
    pfather=NULL;

    return 0;
}
複製程式碼

執行結果:

contructor Father!
contructor Son!
destructor Son!
destructor Father!