基類的析構函式需不需要寫成虛函式

2021-06-01 11:34:54 字數 3039 閱讀 6807

我之前一直認為,基類的析構函式應當是虛函式,否則會留下析構時的隱患,並且在沒有其他虛函式的時候,dynamic_cast將不能工作。

舉個例子,如下,基類base僅僅提供乙個唯一的id來標識乙個例項化的物件,它沒有其他任何使用虛函式的需求。

typedef long classid;

classid gid;

class base

~base()

public:

classid getclassid()

private:

classid mclassid;

};class deriveda : public base

~deriveda()

public:

int getx()

void setx( int x )

int gety()

void sety( int y )

private:

int mx;

int my;

};class derivedaobj : public deriveda

~derivedaobj()

public:

int getraduis()

void setraduis( int raduis )

private:

int mraduis;

};

首先,乙個隱患:

derivedaobj* paobj = new derivedaobj;

deriveda* pa = paobj;

delete pa;

會輸出什麼呢?

deriveda::~deriveda()

base::~base()

而derivedaobj的析構函式並沒有呼叫。假設該類的析構函式實際上對一些堆分配的記憶體進行了清理工作,那麼這種隱患就會造成記憶體洩漏。

其次假設這個時候,我有個方法:

void		printraduis(  deriveda* pobj )

}

這個錯誤是說,deriveda不是乙個多型型別,無法使用dynamic_cast進行動態轉換。我們知道,dynamic_cast的動態轉換依靠的是rtti,該資訊是存在於虛表中。但這裡繼承中沒有使用任何虛函式,因此無法在執行時得到型別資訊。

那麼,是不是一定要給基類,比如base的析構函式加上virtual呢?

如果base::~base()是虛函式的話,那麼首先當子類物件的指標轉換為基類指標後再刪除時,依然會正確的進行析構操作。

比如上面的輸出就會變成:

derivedaobj::~derivedaobj()

deriveda::~deriveda()

base::~base()

其次,dynamic_cast也會正確操作。

這似乎都是成為基類一定要使用虛析構函式的理由,但是,同樣有一些其他的理由試圖說明,沒有必要為沒有需求的基類設定虛析構函式。

class derivedb : public base

~derivedb()

//data block

};

比如base,它僅僅提供了乙個classid,如果宣告虛析構函式,則標誌著所有繼承於它的子類都將有著自己的虛表,特別的,繼承於它的derivedb可能僅僅就是乙個資料類,但它仍然要建立自己的虛表,這是顯然都會降低效能。

而多型的需求,可能並不從繼承鏈的頂端開始,比如對於base的子類deriveda,從這一層開始,可能會使用到多型的特性,那麼,完全可以從這一節點宣告虛析構函式。而不是base類。因為繼承於base類的其他子類(如derivedb)完全沒有必要消耗效能去建立一張虛表。

既然這樣,為了解決正確析構的問題,我們宣告deriveda::~deriveda()為虛析構函式

virtual ~deriveda()

derivedaobj* paobj = new derivedaobj;

deriveda* pa = paobj;

delete pa;

會正確輸出:

derivedaobj::~derivedaobj()

deriveda::~deriveda()

base::~base()

那麼,如果這樣使用呢?

derivedaobj* paobj = new derivedaobj;

base* pb = paobj;

delete pb;

遺憾的是,這只會輸出:

base::~base()

為什麼?因為~base()不是虛函式。

糾結麼?其實不糾結。如果從效能上考慮,的確不需要base使用虛析構函式,那麼就應該將base的析構函式定義為private,以防止對base進行delete操作,如果使用者嘗試這樣做,編譯器將會報錯,強制使用者正確的使用物件,防範隱患的產生。

還有問題,dynamic_cast呢?

事實上,dynamic_cast依賴於rtti,而rtti並不是在所有的情況下都是開啟的,所以本來安全的轉換其實並不一定安全。假如你寫的是個底層庫,你不知道它會用在什麼地方,那個地方是否開啟或是否支援rtti,因此,更安全的方法是自己實現一套rtti的機制。

一般來說,預設開啟rtti,dynamic_cast依賴於虛表中的rtti資訊,如果沒有定義虛函式,編譯器會報錯。並且dynamic_cast在菱形/cross繼承等方面還存在著問題,所以說,dynamic_cast是「不一定安全」的轉換。

結論:

當然,這是見仁見智的看法,通常情況下,我們還是大量使用到動態轉換,但在寫一些特定的元件或底層時,應當慎重考慮rtti的實現問題。

對於基類是否定義虛析構函式的問題,在不關注效能的情況下,定義虛析構函式可以避免delete的隱患。同樣,關注效能的情況下,在未定義虛析構函式的層級,就應該注意將析構函式私有化(private),防止造成隱藏的bug。

為什麼基類的析構函式要寫成虛函式?

答 在實現多型時,當用基類操作派生類,在析構時防止只析構基類而不析構派生類的狀況發生。說明如下 第一段 includeusing namespace std class clxbase clxbase void dosomething class clxderived public clxbase ...

為什麼C 基類析構函式寫成虛函式

下面的 舉例 virtual.cpp 定義控制台應用程式的入口點。include stdafx.h include define maxlen 128 class cemployee cemployee protected private class coperator public cemploy...

為什麼基類的析構函式需要是虛函式?

先來看這句話 只有在通過基類指標 或引用 間接指向派生類子型別時多型性才會起作用。派生類的指標只呼叫自己的函式!基類指標的函式呼叫如果有virtual則根據多型性呼叫派生類的函式,如果沒有virtual則是正常呼叫基類的函式。根據這個結論,設想 如果出現用基類指標指向派生類的情況,那麼在銷毀這個指標...