2013-05-13 21 views
1

正在回答這個問題 - Pure virtual call in destructor of most derived class - 我嘗試了一些代碼來檢查一些語法,並發現當調用sucessive析構函數時,它們會調用它們相關的虛函數。考慮下面的代碼:爲什麼在析構函數中虛擬表設置回該級別?

class Base 
{ 
public: 
virtual void Method() = 0; 
}; 

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

virtual void Method() 
{ 
    cout << "D"; 
} 
}; 

class DoubleD : public Derived 
{ 
public: 

~DoubleD() 
{ 
    Method(); 
} 

virtual void Method() 
{ 
    cout << "DD"; 
} 
}; 

int main(array<System::String ^> ^args) 
{ 
    DoubleD D; 
    DoubleD E; 
    return 0; 
} 

如所預期的,當對象被破壞時,它調用正確的方法(例如,第一最衍生,然後將第二個最導出)。

輸出:DD D

我的問題是,這是爲什麼?既然你不打算在c'tor/d tor中調用虛函數,爲什麼虛表要「正確地展開」呢?

例如,我可以看到爲什麼最派生的工作,這是虛擬函數指針表在啓動時的狀態。但是爲什麼當調用Derived的析構函數時,該表是否正確設置爲指向該類實現Method

爲什麼不只是離開它,或者如果它很好,請將該值設置爲NULL。

+1

「將值設置爲NULL」?設置*什麼*值爲空?!另請注意,C++中沒有「vtable」。這是一個實現細節,如果你已經設法瞭解它,那麼你可能真的很接近學習如何實現析構函數 - 爲什麼你停止閱讀? – 2013-05-13 20:46:48

+0

在附註上,將析構函數標記爲虛擬。使用虛擬功能是一種很好的做法。 – bjskishore123 2013-05-13 20:49:30

+1

「既然你不打算在c'tor/d tor中調用虛函數」:誰告訴你的?建議是要小心地打電話給他們,因爲他們已經「解開」(就像你所說的那樣),可能不會達到你的期望。 (如果它們是純虛擬的,不要打電話給他們;這是不允許的。) – 2013-05-13 21:17:20

回答

0

在創建對象的初始設置後,虛擬表在運行時不會被修改。 在某些實現中,應根據類的基礎創建虛擬表。

在您的示例中,當DoubleD對象被銷燬時,它會調用DoubleD類中的方法函數,因爲該對象的DoubleD部分尚未完全銷燬。 DoubleD類的VTable有一個方法函數指向其類中的方法,因爲它被覆蓋(在最後一級繼承中)

一旦DoubleD被銷燬,現在對象類型的類型是Derived。所以這個調用必須去Derived類的vtable中的方法。因此,行爲。

+0

哦,哇,所以如果你有一個長的繼承鏈,同一個函數被重複覆蓋,對象的大小實際上更大,因爲每個類都有它自己的v-表的副本?這是爲什麼? (純粹是爲了處理這種情況嗎?似乎是OTT,只是將其定義爲未定義的行爲) – 2013-05-13 21:06:02

+2

@ T.Kiley:不,在大多數實現中,每個_class_都有一個表,但該類的所有實例/副本_share_單個表。通常,每個實例/副本都有一個指向當前最派生類型的表的指針,並且在「展開」時它將指針更改爲下一個表。 – 2013-05-13 21:13:33

+1

@ T.Kiley:對象的大小取決於實現。大多數實現通常使用這種「每班vtable」的概念。所有的對象將有v-ptr到一個共享的v-表(該類) – bjskishore123 2013-05-13 21:17:01

2

行爲完全明確。你不應該擔心你的編譯器廠商如何設法實現它(儘管你自己想出來並不是很難,或者只是查找)。

通常不是建議由於非直觀行爲而在析構函數中調用虛函數,但沒有什麼根本錯誤。

5

由於您不打算在c'tor/d'tor中調用虛擬函數,因此爲什麼虛擬表格要「正確地展開」?

前提是錯誤的。從構造函數或析構函數調用虛函數並沒有什麼錯,只要你知道它們是如何工作的。如您所見,動態類型是正在運行的構造函數或析構函數的類型,因此您不會對尚未構建或已銷燬的對象部分進行虛擬調用。

+0

我認爲,因爲大家都說不要在構造函數/析構函數中調用它是未定義的行爲,所以我猜應該檢查這樣的事情! – 2013-05-13 21:06:57

+0

@ T.Kiley:一般來說,如果X不被允許,就不會說「不要做X」。如果不允許X,可以說「X不是有效的C++」。關於做什麼和不做什麼的建議只涉及在語言中有效的事情。這隱含地認爲你絕不能做那些不被允許的事情。 – 2013-05-13 21:16:57

+0

@ T.Kiley:我不知道誰是「每個人」,但他們錯了。如果函數是純虛函數,那只有未定義的行爲。 – 2013-05-13 21:18:57

1

這是如何按照標準工作的。

至於爲什麼,在運行派生類的析構函數之後,不能指望該類的任何屬性有效或一致。如果調用其中一個虛擬方法,如果它進入派生類方法,將會是一場災難。

編譯器很可能會完全繞過vtable,因爲它已經知道哪個重寫方法適用於對象的當前狀態。這只是一個實現細節。

相關問題