虛析構函數、虛函數結合考題變種
1.[Effective c++原則07]:為多態基類聲明virtual 析構函數。
[如果不]: 如果不聲明為析構函數,可能出現的結果如下:Derived對象的成分沒有被銷毀,形成資源洩露、在調試上會浪費很長時間。
class CSimpleClass
{
public:
CSimpleClass(){ cout << "CSimpleClass" <<endl; }
~CSimpleClass() { cout <<"~CSimpleClass" << endl; }
private:
};
class CDerived : public CSimpleClass
{
public:
CDerived() { cout << "CDerived" << endl; }
~CDerived() { cout << "~CDerived" << endl; }
private:
};
int main()
{
CSimpleClass *pSimple = new CDerived;
delete pSimple;
return 0;
}
執行結果如下:
顯然,CDerived 對象沒有被析構!
1、 造成上述不同的原因何在?
“C++標准”明確指出,當派生類對象經由一個基類指針pBaseObject被刪除,而該基類帶有一個non-virtual析構函數,其結果未有定義(即不可預知)。實際執行時,如上面第一個圖示,會產生bug,派生類的對象沒有被銷毀。
這就形成詭異的“局部銷毀”對象,形成資源洩露。
2、 什麼時候需要基類析構函數聲明為虛函數?什麼時候不需要基類的析構函數為虛函數?
該問題涉及析構函數何時應該為虛函數。注意:對於上面的基類BaseClass,
若析構函數不為虛函數,sizeof(BaseClass) = 1。
若析構函數為虛函數,sizeof(BaseClass) = 4。
至於為什麼包含構造函數、非虛析構函數的類的大小為1個字節。解釋如下:
空類類的大小比如BaseClass沒有構造、析構函數,本來sizeof(BaseClass)應該為0,但是我們聲明該類型實例的時候,必須在內存中占用一定的空間,否則無法使用這些實例。至於占用多少內存,由編譯器決定,visual studio中每個空類型的實例占用1個字節的空間。
而加上構造函數、析構函數或其他非虛類型的函數以後呢?由於這些非虛類型的函數的地址只與類有關,而與類的實例無關,編譯器不會因為非虛函數的增加而添加任何額外的信息。
那麼為什麼析構函數變成虛函數後,大小就變成4個字節了呢?主要原因是:C++一旦發現類中有虛函數,就會為該類生成虛函數表,並在該類型的每一個實例中添加指向虛函數表的指針。在32位機器上,一個指針占4個字節的空間,所以求sizeof大小為4。而在64位機器上,一個指針占用8個字節的空間,因此sizeof大小為8。
即為類析構函數聲明為虛析構函數是以付出內存為代價的。所以,無端將所有類的析構函數聲明為虛函數,就向從未聲明它們是虛函數一樣,都是錯誤的。關注盛世游戲http://www.shengshiyouxi.com
總結如下:
(1)帶多態性質的基類應用聲明一個虛析構函數。如果類中帶有任何虛函數,它就應該擁有一個虛析構函數;
(2)設計類的目的如果不作為基類,或者不是為了具備多態性,就不應該聲明虛析構函數。
——參考《Effective C++》條款7;《劍指Offer》
2.[Effective 原則09]:絕不在構造和析構過程中調用virtual函數。
【原因】:base class的執行更早於derived class的構造函數,當base class的構造函數執行的時候derived class的成員變量尚未初始化。
【如果不】:執行的結果不會動態聯編,依然執行其所在層的虛函數。
【示例如下】:
class CSimpleClass
{
public:
CSimpleClass() { cout << "CSimpleClass"<< endl; foo();} //調用了本層的foo
virtual ~CSimpleClass() { cout <<"~CSimpleClass" << endl; foo();} //調用了本層的foo
virtual void foo() { cout << "CSimpleClass::foo()" << endl; }
private:
};
classCDerived : public CSimpleClass
{
public:
CDerived() { cout <<"CDerived" << endl; foo(); }
~CDerived() { cout <<"~CDerived" << endl; foo(); }
void foo() { cout<< "CDerived::foo()" << endl; }
private:
};
int main()
{
CSimpleClass *pSimple = new CDerived;
delete pSimple;
return 0;
}
執行結果如下:
3.綜合1,2的筆試題如下:
class CBase
{
public:
CBase(){ cout << "CBase ctor" << endl; foo(); } //調用本層的foo
~CBase() { cout<< "CBase dtor" << endl; foo(); } //未加virtual,且調用本層的foo
private:
virtualvoid foo(){ cout << "Base::foo()" << endl; } //
};
class CDerived : public CBase
{
public:
CDerived(){cout << "CDerived ctor" << endl; foo(); }
~CDerived(){cout << "CDerived dtor" << endl; foo(); }
private:
virtual void foo(){ cout << "Derived::foo()" << endl; }
};
int main()
{
CBase*pBase = new CDerived;
delete pBase;
return 0;
}