Effective C++筆記:為多態(tài)基類聲明virtual析構(gòu)函數(shù)
就像本文標(biāo)題所說的那樣,應(yīng)該為多態(tài)基類聲明virtual析構(gòu)函數(shù),否則容易造成內(nèi)存泄露。?因為C++明白指出,當(dāng)derived class對象經(jīng)由一個base class指針被刪除,而該base class如果帶有一個non-virtual析構(gòu)函數(shù),其結(jié)果未定義一實際執(zhí)行時通常發(fā)生的是對象的derived成分沒被銷毀。舉個例子:
#includeusing?namespace?std; class?Base { public: ????Base(){?cout?<<?"base?構(gòu)造"?<<?endl;?} ????//??virtual?~Base(){cout<<"base?析構(gòu)"<<endl;}?? ?????~Base(){?cout?<<?"base?析構(gòu)"?<<?endl;?} ????virtual?void?fun(){?cout?<<?"base?fun"?<<?endl;?} private: }; class?Derived?:public?Base { public: ????Derived(){?cout?<<?"drived?構(gòu)造"?<<?endl;?} ????~Derived(){?cout?<<?"drived?析構(gòu)"?<<?endl;?} ????void?fun(){?cout?<<?"drived?fun"?<<?endl;?} }; int?main() { ????Base?*pd?=?new?Derived(); ????pd->fun();//打印drived?fun,實現(xiàn)多態(tài) ????delete?pd; ????system("pause"); }
? ? ? ?這個程序的運行結(jié)果是:
? ? ? ?并沒有調(diào)用drived class的析構(gòu)函數(shù),然而其base class成分通常會被銷毀,于是造成一個詭異的"局部銷毀"對象,從而造成內(nèi)存泄露 。
? ? ? ?如果將基類的析構(gòu)函數(shù)聲明為virtual,那么就OK了!?
? ? ? ?相反地,如果這個類并不是基類,那么不要將其析構(gòu)函數(shù)聲明為virtual函數(shù)。原因與virtual函數(shù)的實現(xiàn)機制:虛函數(shù)表有關(guān)。簡單的說,如果要實現(xiàn)虛函數(shù),那么這個對象就會附帶一個虛函數(shù)表指針指向一個由函數(shù)指針組成的數(shù)組,這個數(shù)組就是虛函數(shù)表,當(dāng)對象使用虛函數(shù)時,需要從這張表中尋找適當(dāng)?shù)暮瘮?shù)指針,這大大的增加了開銷。
? ? ? ?我們還容易犯這樣的錯誤:繼承標(biāo)準(zhǔn)庫中的某個類。但是,標(biāo)準(zhǔn)庫中的很多類是不含virtual函數(shù)的!比如string,STL的容器等等。所以如果你這樣寫:?
class?MyString:public?string?? {?? public:?? ????MyString(string?str,int?i):s(str),length(i){}?? ????~MyString(){cout<<"MyString?析構(gòu)函數(shù)"<<endl;}?? private:?? ????string?s;?? ????int?length;?? };
若如此調(diào)用時就會造成和上面類似的風(fēng)險:
string*?pStr?=?new?MyString("aaa",1);? delete?pStr;
? ? ? ?總之,如果這這個基類在派生過程中要實現(xiàn)多態(tài),那么就需要把它的析構(gòu)函數(shù)聲明為virtual;如果這個類并不是用作基類或者并不是實現(xiàn)多態(tài),那么就不要把它的析構(gòu)函數(shù)聲明為virtual。