2010-07-16 42 views
25

析構函數(當然也是構造函數)和其他成員函數之間的區別在於,如果常規成員函數在派生類中具有正文,則僅執行Derived類中的版本。而在析構函數的情況下,派生以及基類版本都會被執行?爲什麼在刪除派生類對象時調用基類析構函數(虛擬)?

在析構函數(可能是虛擬的)的情況下,知道究竟發生了什麼真是太好了,即使刪除了大多數派生類對象,也要調用它們的所有基類。

在此先感謝!

+0

不完全是你想要的,但它顯示了編譯器生成的析構函數會做什麼。 http://stackoverflow.com/questions/1810163/c-copy-constructor-a-class-that-c​​ontains-other-objects/1810320#1810320 – 2010-07-16 05:09:46

回答

13

標準說

執行析構函數的身體和破壞人體內部分配的任何自動對象後, 一類X的析構函數調用了X的直接非變體成員,析構函數析構函數對於X的直接 基類如果X是派生類最多的類型(12.6.2),那麼它的析構函數將調用 X的虛擬基類的析構函數。所有析構函數都被調用,就像它們被引用了一個合格的名稱,即 忽略了更多派生類中的任何可能的虛擬覆蓋析構函數。 以其構造函數(參見12.6.2)完成的相反順序銷燬基礎和構件 。析構函數中的return語句(6.6.3)可能不會直接返回給調用者;在將控制轉移給調用者之前,調用成員和基礎的析構函數 。數組元素的析構函數按 的相反順序調用(參見12.6)。

另外,作爲每RAII資源需要被綁定到合適的對象的生存期和各自的類必須被要求以釋放資源的析構函數。

例如下面的代碼泄漏內存。

struct Base 
{ 
     int *p; 
     Base():p(new int){} 
     ~Base(){ delete p; } //has to be virtual 
}; 

struct Derived :Base 
{ 
     int *d; 
     Derived():Base(),d(new int){} 
     ~Derived(){delete d;} 
}; 

int main() 
{ 
    Base *base=new Derived(); 
    //do something 

    delete base; //Oops!! ~Base() gets called(=>Memory Leak). 
} 
+0

@dreamlax:呵呵,謝謝,編輯:) – 2010-07-16 03:58:54

+0

*形成不良*表示它沒有編譯(編輯,意思是說沒有編譯)。上面的代碼會編譯,只是漏洞。 – 2010-07-16 04:02:19

+0

@Igor:我從來沒有說過代碼不會編譯。我明確提到代碼有內存泄漏。 – 2010-07-16 04:03:28

11

這是設計。必須調用基類上的析構函數才能釋放它的資源。經驗法則是,派生類應該只清理自己的資源,並讓基類自行清理。

C++ spec

執行 析構函數的身體和破壞 體內分配的任何 自動對象之後,對於X類析構函數調用 的析構函數X的直接 成員,析構函數對於X的 直接基類,如果X是 類型的最派生類 (12.6.2),則其析構函數調用 析構函數爲X的虛擬基地 c lasses。如果所有析構函數都以 限定名稱引用,即忽略更多派生類中的任何 可能的虛擬重寫 析構函數,則稱它們爲 。 基地和會員在 相反順序銷燬 其構造函數(見 12.6.2)。

此外,因爲只有一個析構函數,所以類不得不調用哪個析構函數。構造函數並非如此,如果沒有可訪問的默認構造函數,程序員必須選擇應調用哪個基類構造函數。

+0

*「程序員必須選擇哪個基類構造函數」* - 這是隻有在沒有可訪問的默認構造函數的情況下。 – 2010-07-16 04:45:23

+0

正確,讓我澄清一點。 – 2010-07-16 04:50:37

2

因爲這就是dtor的工作方式。當你創建一個對象時,ctors從基礎開始被調用,並且一直到最大派生。當你正確地銷燬對象時,會發生相反的情況。虛擬製作虛擬機的時間有所不同,如果/當你通過指針(或引用,儘管這很不尋常)摧毀一個對象到基本類型時。在這種情況下,替代方案並不是真的只有派生的dtor被調用 - 相反,替代方案只是未定義的行爲。這種做法恰好採取只調用派生的dtor的形式,但它也可能採用完全不同的形式。

2

正如伊戈爾所說的構造函數必須被調用基類。考慮會發生什麼,如果它就不叫:

struct A { 
    std::string s; 
    virtual ~A() {} 
}; 

struct B : A {}; 

如果刪除B實例時A析構函數不會被調用,A永遠不會被清理。

2

基類析構函數可能負責清理由基類構造函數分配的資源。

如果您的基類有一個默認的構造函數(一個不帶參數或者沒有其所有參數的默認值),那麼在構造派生實例時會自動調用該構造函數。

如果您的基類具有需要參數的構造函數,則必須在派生類構造函數的初始化程序列表中手動調用它。

由於析構函數不帶參數,所以在刪除派生實例時,總會自動調用基類析構函數。

如果您在使用多態性與派生實例由基類指針指向,那麼衍生如果基析構函數是虛擬的類析構函數只調用。

0

當任何對象被銷燬時,析構函數會針對所有子對象運行。這包括通過遏制來重用和通過繼承來重用。

7

構造函數和析構函數與其他常規方法不同。

構造

  • 不能在派生類中的虛擬
  • 你要麼顯式調用基類
  • 的構造函數,或在情況下你不調用基類的構造函數編譯器將插入呼叫。它將調用沒有參數的基礎構造函數。如果不存在這樣的構造函數,則會出現編譯器錯誤。

struct A {}; 
struct B : A { B() : A() {} }; 

// but this works as well because compiler inserts call to A(): 
struct B : A { B() {} }; 

// however this does not compile: 
struct A { A(int x) {} }; 
struct B : A { B() {} }; 

// you need: 
struct B : A { B() : A(4) {} }; 

析構

    當調用超過一個指針或參考,其中,所述基類有虛擬析構派生類的析構函數
  • ,最衍生析構函數將是先調用,然後按照構造的相反順序調用其餘的派生類。這是爲了確保所有內存都已經被正確清理。如果派生類的最後一個被調用,那麼它將不起作用,因爲那時基類不會在內存中存在,並且會出現段錯誤。

struct C 
{ 
    virtual ~C() { cout << __FUNCTION__ << endl; } 
}; 

struct D : C 
{ 
    virtual ~D() { cout << __FUNCTION__ << endl; } 
}; 

struct E : D 
{ 
    virtual ~E() { cout << __FUNCTION__ << endl; } 
}; 

int main() 
{ 
    C * o = new E(); 
    delete o; 
} 

輸出:

~E 
~D 
~C 

如果在基類中的方法被標記爲virtual所有繼承的方法是虛擬的,以及因此,即使您沒有標記的析構函數DE作爲virtual他們仍然是virtual,他們仍然被調用以相同的順序。

相關問題