2014-11-04 132 views
1

我以前見過這個問題,但並不清楚,或者與我遇到過的情況相同。C++非抽象析構函數繼承

我有一個抽象基類。它有一個受保護的構造函數和一個析構函數。它由幾個完整類型繼承,這些類型也具有公共構造函數和析構函數。我遇到的問題是,如果對象被基類型引用,那麼刪除對象不會調用子析構函數。

class Tree 
{ 
protected: 
     Tree(){ } 
public: 
     ~Tree(){ } 
}; 

class OakTree : public Tree 
{ 
public: 
     OakTree(){ } 
     ~OakTree(){ } 
}; 

vector<Tree*> Trees; // Store objects using the base type 
Trees.push_back(new OakTree()); // Create derived object 
delete Trees[0]; // OakTree desctructor does not get called 

我該如何獲得OakTree析構函數?我試圖將所有的析構函數都標記爲虛擬的,但這並不起作用。基類析構函數不能是抽象的(這將解決調用問題,但不解決刪除問題)。

+0

對不起,我不得不使其顯示在我的記憶中轉儲內存泄漏讓我覺得析構函數沒有被調用析構函數的一個內部錯誤。我99%肯定在這種情況下析構函數應該是虛擬的,但是我把它們作爲虛擬的,但是這並沒有解決我的內存泄漏問題。不小心在我的部分,但爲你的答案+1。 – 2014-11-04 16:14:42

回答

7

讓您的基礎級析構函數virtual

class Tree 
{ 
protected: 
     Tree(){ } 
public: 
     virtual ~Tree(){ } 
} 

否則,如果您嘗試通過基類指針進行刪除,則會導致未定義的行爲。這是一個有點過時,但斯科特邁爾斯在有效的C++五顏六色表達了這樣的,第2版:

C++語言標準是關於這一主題異常清晰:當您試圖通過一個基類中刪除一個派生類對象指針和基類有一個非虛擬的析構函數(如EnemyTarget那樣),結果是未定義的。這意味着編譯器可以生成代碼以執行任何他們喜歡的操作:重新格式化磁盤,向您的老闆發送暗示性郵件,將源代碼傳真給您的競爭對手,無論如何。 (運行時經常發生的是派生類的destrutcor從來沒有被調用......)

+0

這實際上是特定於實現的,我相當肯定現有的實現不會隨機調用函數,特別是格式化磁盤驅動器。我測試過的編譯器稱爲「最接近的東西」,它是指針T的析構函數。 – dtech 2014-11-04 16:25:05

+0

這是*誇張*。然而,未定義的行爲可能是一種安全風險,在某些情況下可以利用這種風險來實現各種攻擊者的目標。 – 2014-11-04 16:34:12

+0

只是不要認爲誇張在技術文獻中有任何地方,我認爲邁耶斯先生在他的許多陳述中試圖過於「古怪」/有趣且不必要的多彩。我越來越討厭這種行爲,因爲那些傾向於花費更多時間在「可感知的青年」面前培養幻想形象的講座,而不是實際教授這個主題的講課熱衷於教授。 – dtech 2014-11-04 16:42:26

3

這是因爲你的基類析構函數沒有被聲明爲虛函數。

class Tree 
{ 
protected: 
     Tree(){ } 
public: 
     virtual ~Tree(){ } 
} 
0

如果你要使用多態,你必須在你的基類中有一個虛析構函數。

問題是,當你調用Tree *上的刪除時,沒有人真的知道它是什麼類型的樹,並且通過具有虛擬析構函數,編譯器通過指向該函數指針的指針生成一個調用,以獲取該特定對象的函數實例,並且你可以得到任何類型的析構函數。

否則該標準說它是未定義的行爲,但似乎大多數情況下發生的事情是編譯器生成一個對任何類型多態指針或引用的析構函數的調用,在你的情況下,這是基類。

0

我想知道爲什麼沒有人真的問作者想用代碼實現什麼?

雖然,是的,有在基類虛析構函數會幫助你得到你叫析構函數,什麼你仍然留下的是未定義行爲

你認爲該行delete Trees[0];後的結果如何?

或者,你試圖用這條線實現什麼?

如果你想刪除Vector中的項目,你可能想:

Trees.erase(Trees.begin()); 

但後來,你留下了內存泄漏,如果你忘記刪除線。

因此,請不要在向量中使用普通指針。試想一下:

std::vector< std::shared_ptr<Tree> > Trees; 
Trees.push_back(std::make_shared<OakTree>()); 
Trees.erase(Trees.begin()); 

的Runnable @Ideone

+0

這是來自大型項目的簡化代碼。基類存儲一些數據,派生類對數據進行多種操作。派生類還有本地資源,需要在對象的生命週期結束時刪除。我意識到媒介如何工作,而這不是問題的一部分。 – 2014-11-04 16:45:11

+0

內存管理呢?如果只是針對析構函數,那麼在你的問題中不需要任何向量代碼 – 2014-11-04 16:52:51

+0

只是爲了引起人們注意,我將它存儲爲'Tree *'而不是'OakTree *'或'Tree'。讓我們不要太挑剔。問題得到解答。 – 2014-11-04 16:55:49