虛析構函數 析構函數的工作方式是:最底層的派生類(most derived class)的析構函數最先被調用,然後調用每一個基類的析構函數。 因為在C++中,當一個派生類對象通過使用一個基類指針删除,而這個基類有一個非虛的析構函數,則結果是未定義的。運作時比較有代表性的後果是對象的派生部分不會被銷毀。然而,基類部分很可能已被銷毀,這就導緻了一個古怪的“部分析構”對象,這是一個洩漏資源。排除這個問題非常簡單:給基類一個虛析構函數。于是,删除一個派生類對象的時候就有了你所期望的正确行為。将銷毀整個對象,包括全部的派生類部分。 但是,一般如果不做基類的類的析構函數一般不聲明為虛函數,因為虛函數的實作要求對象攜帶額外的資訊,這些資訊用于在運作時确定該對象應該調用哪一個虛函數。典型情況下,這一資訊具有一種被稱為 vptr(virtual table pointer,虛函數表指針)的指針的形式。vptr 指向一個被稱為 vtbl(virtual table,虛函數表)的函數指針數組,每一個包含虛函數的類都關聯到 vtbl。當一個對象調用了虛函數,實際的被調用函數通過下面的步驟确定:找到對象的 vptr 指向的 vtbl,然後在 vtbl 中尋找合适的函數指針。這樣子會使類所占用的記憶體增加。
定義純虛析構函數(pure virtual destructor)
純虛成員函數通常沒有定義;它們是在抽象類中聲明,然後在派生類中實作。比如說下面的例子:
class File //an abstract class
{
public:
virtual int open(const string & path, int mode=0x666)=0;
virtual int close()=0;
//...
}; 但是,在某些情況下,我們卻需要定義一個純虛成員函數,而不僅僅是聲明它。最常見的例子是純虛析構函數。在聲明純虛析構函數時,不要忘了同時還要定義它。 class File //abstract class
{
public:
virtual ~File()=0; //declaration of a pure virtual dtor
};
File::~File() {} //definition of dtor 為什麼說定義純虛析構函數是非常重要的
派生類的析構函數會自動調用其基類的析構函數。這個過程是遞歸的,最終,抽象類的純虛析構函數也會被調用。 如果純虛析構函數隻被聲明而沒有定義,那麼就會造成運作時(runtime)崩潰。(在很多情況下,這個錯誤會出現在編譯期,但誰也不擔保一定會是這樣。)純虛析構函數的啞元實作(dummy implementation,即空實作)能夠保證這樣的代碼的安全性。 class DiskFile : public File
{
public:
int open(const string & pathname, int mode);
int close();
~DiskFile();
}; File * pf = new DiskFile;
//. . .
delete pf; //OK, ultimately invokes File::~File() 在某些情況下定義其它純虛成員函數可能也是非常有用的(比如說在調試應用程式以及記錄應用程式的日志時)。例如,在一個不應該被調用,但是由于一個缺陷而被調用的基類中,如果有一個純虛成員函數,那麼我們可以為它提供一個定義。 class Abstract
{
public:
virtual int func()=0;
//..
};
int Abstract::func()
{
std::cerr<<"got called from thread " << thread_id<<
"at: "<<gettimeofday()<<std::endl;
} 這樣,我們就可以記錄所有對純虛函數的調用,并且還可以定位錯誤代碼;不為純虛函數提供定義将會導緻整個程式無條件地終止。