2010-04-27 110 views
6

幾個小時後我擺弄着一個內存泄漏問題,事實證明我真的有一些關於虛擬析構函數的基本問題是錯誤的!讓我解釋我的課程設計。虛擬析構函數如何工作?

class Base 
{ 
    virtual push_elements() 
    {} 
}; 

class Derived:public Base 
{ 
vector<int> x; 
public: 
    void push_elements(){ 
     for(int i=0;i <5;i++) 
     x.push_back(i); 
    } 
}; 

void main() 
{ 
    Base* b = new Derived(); 
    b->push_elements(); 
    delete b; 
} 

邊界檢查器工具在派生類向量中報告了內存泄漏。我發現析構函數不是虛擬的,派生類析構函數沒有被調用。當我把析構函數虛擬化時,它驚人地得到了修復。即使派生類析構函數未被調用,矢量是否不會自動釋放?這是BoundsChecker工具中的一個怪癖還是我對虛擬析構函數的理解錯誤?

+1

請不要使用HTML格式化您的代碼。選擇並按下'0101'按鈕,將會縮進4個空格。 – Yacoby 2010-04-27 15:31:04

+0

發佈的代碼與Base和派生之間沒有任何關係 – JRL 2010-04-27 15:31:10

+0

@JRL:謝謝。派生來自Base。我已經改變了.. – Prabhu 2010-04-27 15:32:40

回答

15

當基類沒有虛擬析構函數時,通過基類指針刪除派生類對象會導致未定義的行爲。你所觀察到的(對象的派生類部分永遠不會被銷燬,因此其成員永遠不會被釋放)可能是許多可能行爲中最常見的,也是一個很好的例子,說明爲什麼它很重要當你以這種方式使用多態時,確保你的析構函數是虛擬的。

1

如果析構函數不是虛擬的,那麼將調用Base析構函數。基本析構函數清理Base對象並完成。基礎對象析構函數沒有辦法知道派生對象,它必須是所調用的派生析構函數,並且與任何函數一樣,這樣做的方式是使析構函數爲虛擬。

+0

事實並非如此,正如我和其他人所指出的,你得到的是未定義的行爲,這並不一定意味着調用基本析構函數。 – 2010-04-27 15:40:01

+1

僅調用基本析構函數是儘可能在此處調用未定義行爲的最可能的表現,這對於調試目的而言是有幫助的。 – 2010-04-27 15:45:40

+0

@ Neil:是的,但我期望大部分時間都會調用基類析構函數,就像我期望的那樣'int i = 3; i = i ++ + i ++;'離開包含一個小整數的'i',而沒有其他狀態改變。在這種情況下,這很重要,因爲合理的行爲會導致觀察結果,並增強虛擬析構函數缺乏導致問題的可能性。把它看作更多的診斷問題,而不是一致性問題。 – 2010-04-27 15:47:42

8

如果基類沒有虛析構函數,那麼你的代碼的結果是未定義的行爲,不一定是被調用的錯誤的析構函數。這大概是BoundsChecker正在診斷的。

+4

寵壞孩子在技術上正確的答案並不是一種誘人的奇觀。 – 2010-04-27 15:51:05

2

雖然這在技術上是不確定的,但您仍然需要知道最常見的故障診斷方法,以便對其進行診斷。這種常見的失敗方法是調用錯誤的析構函數。我不知道任何會以任何其他方式失敗的實現,但承認我只使用兩個實現。

發生這種情況的原因與嘗試覆蓋非虛擬成員函數並通過基指針調用它時調用「錯誤」函數的原因相同。

1

來自C++ FAQ Lite:「什麼時候我的析構函數是虛擬的?」請閱讀here。順便提一句,C++ FAQ Lite是所有與C++相關的問題的絕佳來源。

+1

C++常見問題解答書甚至更好。 – 2010-04-27 16:18:21

1

在C++中,瑣碎析構函數是一個遞歸定義的概念 - 它是編譯器在類(和每個基類)的每個成員都有一個微不足道的析構函數時爲您寫的析構函數。 (有一個叫做平凡的構造類似的概念。)

當一個平凡的析構函數的對象包含在一個對象(如在您的示例vector),再外面的物品(如你Derived)的析構函數是不再微不足道了。即使你沒有編寫析構函數,C++編譯器也會自動編寫一個析構函數,它調用任何具有析構函數的成員的析構函數。

所以,即使你沒有寫任何東西,編寫非虛擬析構函數的注意事項仍然適用。

0

Destructor是類的成員函數,其名稱與類名稱相同,並且前面帶有代字號(〜)。析構函數用於在對象超出範圍時銷燬類的對象,或者可以說所有清理類的銷燬都要在析構函數中完成。當對象超出範圍時,所有內存在構造類中的對象時被分配(或釋放內存)。

查找BoundsCheck

1

與例如更多的細節,如果你是從C#來了,那麼你就在想,爲什麼矢量不會自動取消分配。但在C++中,除非您使用Microsoft Managed Extesions to C++(C++/CLI),否則無法實現自動內存管理。

由於在基類中沒有析構函數是虛擬的,所以派生類對象將永遠不會被釋放,並且通過泄漏分配給派生類的向量數據成員的內存。