Declare destructors virtual in polymorphic base classes.
C++指出,當derived class對象經由一個由base class類型的指針刪除時,若是這個base class 擁有一個non-virtual的析構函數,那個析構的結果將是未定義的。即一般狀況下是該對象的base class成分會被析構掉,可是其derived class成分沒有被銷燬,甚至連derived class的析構函數也不會被調用。
因而造成一個被「局部銷燬」的對象,形成資源泄漏。
例如:
class car
{
public:
car();
~car();
...
};
class diesel_car : public car {…};
class solar_car: public car {…};
class electric_car : public car {…};
當客戶代碼中使用汽車對象時,若是他不關心使用的是具體哪一類汽車這個細節,那麼咱們能夠設計一個工廠函數(或者工廠類)負責建立一個汽車對象,該工廠函數返回一個base class指針或者引用,指向新生成的derived class對象:
爲遵照工廠函數的規矩,返回的對象必須位於heap(不然函數返回的指針在函數返回後將指向一個非法的位置,由於位於stack的對象的生命週期爲函數域),所以爲了不內存泄漏,須要客戶代碼將工廠函數返回的對象適當地delete掉:java
car* p_car = get_car(); // 從car繼承體系中得到一個動態分配對象
… // 使用這個對象
delete p_car; // 釋放這個對象以免內存泄漏
首先須要說明,上述作法已經存在兩個缺陷:c#
1.依賴客戶代碼執行delete操做,帶有錯誤傾向,客戶可能會忘記作這件事。
2.工廠函數結構應該考慮預防常見的客戶代碼錯誤。
可是最根本的弱點在於:客戶代碼根本沒法將返回的derived class對象完全銷燬。
簡單的作法即是:爲base class定義一個virtual析構函數。此後刪除derived class對象就會銷燬這個對象,包括全部的derived class成分。
class car
{
public:
car();
virtual ~car();
...
};
當一個類須要被用做多態(Polymorphism)時,就應該爲該類聲明一個virtual析構函數,即任何class只要帶有virtual函數都幾乎肯定應該也有一個virtual析構函數。
可是,若是class沒有virtual函數,即不被用做多態用途,一般意味着它並不意圖被用做一個base class(除了某些特殊狀況,如noncopyable類)。當class不被用做base class時,最好不要爲其定義一個析構函數。
由於C++中將函數定義爲virtual是有代價的,這個代價就是虛表指針virtual table pointer。
欲實現virtual函數,對象必須攜帶某種信息,用於在運行期決定調用哪個virtual函數。這份信息一般是由一個所謂的vptr(virtual table pointer)指針指出。vptr指向一個有函數指針構成的數組,成爲vtbl(virtual table);每個帶有virtual函數的class都有一個相應的vtbl。當對對象調用某一virtual函數,是及被調用的函數取決於該對象的vptr所指向的那個vtbl——編譯器在其中尋找適當的函數指針。
所以,每個定義了virtual函數的class的對象都包含一個vptr。這樣一來,對象的體積會由於virtual函數的存在而增長。
例如:
class point
{
public:
point(int coord_x, int coord_y);
~point();
private:
int x, y;
};
32位系統中,int類型佔32bits,所以point對象一共佔64bits,能夠被塞入一個64bit緩存器中,甚至能夠被看成一個「64bit 量」傳給其餘語言如C活着FORTRAN編寫的函數。
可是若是point內含析構函數時,point對象佔用的空間將是96bits,(2個ints加1個vptr)。對象體積從64bits增長到96bits。
而在64bit計算機體系結構中,point對象將佔用128bits(由於指針類型佔用64bits)。對象體積從64bits增長到128bits。
這樣的對象將沒法被塞入一個64-bit緩存器中,而C++的point對象也再也不和其餘語言(如C)內的相同聲明有着同樣的結構,所以也就沒法將其傳遞到其餘語言編寫的函數中,所以再也不有移植性。
所以,將不用做多態用途的class的析構函數聲明爲virtual是不合理的。只有當class內至少含有一個virtual函數時才應該將其析構函數聲明爲virtual。
不要試圖繼承任何帶有non-virtual析構函數的類,包括全部STL容器如vector,list, set, unordered_map, string等等。由於這會致使資源泄漏!
不幸的是C++中沒有提供相似java的final classes或者c#中的sealed classes那樣的「禁止派生」機制。
當但願將一個class定義爲抽象class(pure virtual class),但有沒有任何pure virtual函數時,爲這個class聲明一個pure virtual析構函數是很便利的。
class abstract_class
{
public:
virtual ~abstract_class() = 0;
};
可是要注意:必須爲這個pure virtual析構函數提供一份定義:
abstract_class::~abstract_class(){}
由於析構函數的運做方式是:最深層派生(most derived)的那個class的析構函數最早被調用,而後是其每個base class的析構函數被調用。編譯器會在 abstract_class的derived classes中建立一個對~abstract_class的調用動做,因此必須爲~abstract_class提供定義,不然連接器會報錯。
1.polymorphic (帶多態性質的)base classes 應該聲明一個virtual析構函數。若是class 帶有任何virtual函數,就應該爲其聲明一個virtual析構函數。由於這樣的base class設計出來的目的就是用來「經過base class 接口處理derived class對象」。
2.有些class本來就不是設計做爲base class使用,或者就算是做爲base class 也不具有多態性,這樣的class就不該該聲明爲virtual析構函數。
默認生成的析構函數是public且non-virtual的。