簡體   English   中英

虛擬析構函數和內存釋放

[英]Virtual destructor and memory deallocation

我不太確定我是否理解虛擬析構函數和在堆上分配空間的概念。 讓我們看看下面的例子:

class Base
{
public:
    int a;
};

class Derived : public Base
{
public:
    int b;
};

我想如果我做這樣的事情

Base *o = new Derived;

在堆上分配了 8 個字節(或系統上需要的任何兩個整數),看起來像這樣: ... | | 乙 | ...

現在,如果我這樣做:

delete o;

'delete' 如何知道實際上哪種類型 o 是為了從堆中刪除所有內容? 我想它必須假設它是 Base 類型,因此只從堆中刪除 a(因為它不能確定 b 是否屬於對象 o): ... | 乙 | ...

b 將保留在堆上並且無法訪問。

是否執行以下操作:

Base *o = new Derived;
delete o;

真的會引起內存泄漏,我需要在這里使用虛擬析構函數嗎? 還是 delete 知道 o 實際上是派生類,而不是基類? 如果是這樣,它是如何工作的?

謝謝你們。 :)

您對實現做出了很多假設,這些假設可能成立,也可能不成立。 delete表達式中,動態類型必須與靜態類型相同,除非靜態類型具有虛擬析構函數。 否則,它是未定義的行為。 時期。 這就是你真正需要知道的——我已經使用過否則它會崩潰的實現,至少在某些情況下; 並且我使用過這樣做會破壞可用空間領域的實現,因此代碼會在稍后的某個時間崩潰,在一段完全不相關的代碼中。 (根據記錄,VC++ 和 g++ 都屬於第二種情況,至少在使用已發布代碼的常用選項編譯時是這樣。)

首先,您在示例中聲明的類具有簡單的內部結構。 從純實用的角度來看,為了正確銷毀此類類的對象,運行時代碼不需要知道被刪除對象的實際類型 它只需要知道要釋放的內存塊的正確大小 這實際上是 C 風格的庫函數(如mallocfree )已經實現的。 您可能知道, free隱式地“知道”要釋放多少內存。 除此之外,您上面的示例不涉及任何其他內容。 換句話說,您上面的示例不夠詳盡,無法真正說明任何特定於 C++ 的內容。

但是,正式地,您的示例的行為是未定義的,因為 C++ 語言正式要求虛擬析構函數進行多態刪除,而不管類的內部結構有多么微不足道。 因此,您的“如何delete知道...”問題根本不適用。 你的代碼壞了。 這沒用。

其次,當您開始要求對類進行非平凡的析構時,實際的 C++ 特定效果就會開始出現:通過為析構函數定義顯式主體或向類中添加非平凡的成員子對象。 例如,如果向派生類添加std::vector成員,則派生類的析構函數將負責(隱式)銷毀該子對象。 為了讓它起作用,你必須聲明你的析構函數virtual 通過與調用任何其他虛擬函數相同的機制調用適當的虛擬析構函數。 這基本上就是你問題的答案:運行時代碼並不關心對象的實際類型,因為普通的虛擬調度機制將確保調用正確的析構函數(就像它與任何其他虛擬函數一起工作一樣)。

第三,當你為你的類定義專用的operator delete函數時,虛擬破壞的另一個重要影響就會出現。 語言規范要求選擇正確的operator delete函數,就像從被刪除類的析構函數內部查找一樣。 許多實現從字面上實現了這個要求:它們實際上從類析構函數內部隱式調用operator delete 為了使該機制正常工作,析構函數必須是虛擬的。

第四,您的問題的一部分似乎表明您認為未能定義虛擬析構函數將導致“內存泄漏”。 這是一個流行的,但完全不正確和完全無用的都市傳說,由低質量的來源延續。 在沒有虛擬析構函數的類上執行多態刪除會導致未定義的行為和完全不可預測的破壞性后果,而不是一些“內存泄漏”。 在這種情況下,“內存泄漏”不是問題。

被刪除的對象的大小沒有問題 - 這是已知的。 虛擬析構函數解決的問題可以證明如下:

class Base
{
public:
    Base() { x = new char[1]; }
    /*virtual*/ ~Base() { delete [] x; }

private:
    char* x;
};

class Derived : public Base
{
public:
    Derived() { y = new char[1]; }
    ~Derived() { delete [] y;}
private:
    char* y;
};

然后有:

Derived* d = new Derived();
Base* b = new Derived();

delete d;   // OK
delete b;   // will only call Base::~Base, and not Derived::~Derived

第二次刪除不會正確完成對象。 如果取消注釋virtual關鍵字,則第二個delete語句將按預期運行,它將調用Derived::~DerivedBase::~Base

正如評論中指出的那樣,嚴格來說,第二次刪除會產生未定義的行為,但在這里使用它只是為了說明虛擬析構函數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM