虛解構函式、虛函數結合考題變種
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。
即為類解構函式聲明為虛解構函式是以付出記憶體為代價的。所以,無端將所有類的解構函式聲明為虛函數,就向從未聲明它們是虛函數一樣,都是錯誤的。
總結如下:
(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,且調用本層的fooprivate: 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;}
結合原則1,2.正確的輸出結果是:
顯然,1.CDerived的解構函式不會被調用,因為CBase的解構函式非虛函數。
2.在CBase的構造和解構函式中調用虛函數,僅會執行本層的定義,不會下調。還是遵照[Effective 原則09]:絕不在構造和析構過程中調用virtual函數為上策。