effective C++ 筆記:條款07 為多態(tài)基類聲明virtual析構(gòu)函數(shù)

考慮以下程序

class base {
public:
    base() {
        cout << "base constructor" << endl;
    }
    ~base() {
        cout << "base destructor" << endl;
    }
};

class derived : public base {
public:
    derived() {
        cout << "derived constructor" << endl;
    }
    ~derived() {
        cout << "derived destructor" << endl;
    }
};
int main()
{
    base* b = new derived();
    delete b;
    while(true){}
    return 0;
}

該段代碼的輸出為


image.png

也就是說,析構(gòu)時(shí)并沒有調(diào)用子類的析構(gòu)函數(shù),造成了一個(gè)詭異的“局部銷毀”現(xiàn)象。
解決方法就是使基類的析構(gòu)函數(shù)稱為虛函數(shù)。

virtual ~base() {
        cout << "base destructor" << endl;
    }

這樣,輸出為


image.png

子類部分得到了正確的析構(gòu)。
但是值得注意的是,c++并沒有默認(rèn)析構(gòu)函數(shù)是虛函數(shù)。因?yàn)椴⒉皇撬械念惗际菫榱吮焕^承而生的,也就是說,它們不會(huì)被作為基類,那么也就不會(huì)出現(xiàn)以上“局部銷毀”的情況,而此時(shí)析構(gòu)函數(shù)若默認(rèn)為虛函數(shù),那么該類中會(huì)生成一個(gè)vptr(虛表指針),這個(gè)vptr的產(chǎn)生是毫無意義的(因?yàn)檫@個(gè)類根本不會(huì)用這個(gè)vptr),也就是說白白浪費(fèi)了一個(gè)指針?biāo)嫉膬?nèi)存空間,假設(shè)程序中有很多這樣的類,那么浪費(fèi)的內(nèi)存空間就比較可觀了。
許多人的心得是:當(dāng)一個(gè)class內(nèi)至少有一個(gè)virtual函數(shù)時(shí),就將析構(gòu)函數(shù)聲明為虛函數(shù)。

通過以上,我們知道了一個(gè)基類,它的析構(gòu)函數(shù)應(yīng)當(dāng)為虛析構(gòu)函數(shù),那么問題來了,有時(shí)我們并不會(huì)考慮這么周全,請(qǐng)看以下代碼

class newString : public string {
public:
    newString() {
        cout << "newString constructor" << endl;
    }
    ~newString() {
        cout << "newString destructor" << endl;
    }
};
int main()
{
    string* a = new newString();
    delete a;
    while(true){}
    return 0;
}
image.png

我們繼承了一個(gè)“系統(tǒng)給的類” string,我們可能就會(huì)忘記以上的教訓(xùn),在這種情況下,delete一個(gè)指向newString的string類型的指針就會(huì)產(chǎn)生“局部銷毀”,導(dǎo)致內(nèi)存泄漏。 不僅僅是string,換作包括STL的容器如vector,list, set等等,情況也是相同的。
所以如果你設(shè)計(jì)了一個(gè)類,并且這個(gè)類不打算作為基類被繼承的,那么請(qǐng)聲明其為final(c++11新特性)

class Nbase final{
};

還有一個(gè)注意事項(xiàng)是,如果將基類析構(gòu)函數(shù)聲明為虛析構(gòu)函數(shù),那么這個(gè)析構(gòu)函數(shù)必須有定義,否則會(huì)報(bào)連接錯(cuò)誤。
尤其要注意,如果把這個(gè)析構(gòu)函數(shù)聲明為純虛函數(shù)(此時(shí)這個(gè)類為抽象類)

virtual ~derived() = 0;

那么就必須顯式地在類外定義這個(gè)析構(gòu)函數(shù)

base::~base(){}

不是說如果析構(gòu)函數(shù)只是普通的虛函數(shù)而不是純虛函數(shù)就不用提供定義,而是我們給普通的虛析構(gòu)函數(shù)提供定義比較順手罷了(在聲明后面直接加{})。
而聲明為純虛函數(shù)的話容易忘記,假如在程序龐大的情況下,報(bào)一個(gè)連接錯(cuò)誤我相信是很讓人抓狂的,有時(shí)候并不會(huì)想到只是缺少了一個(gè)純虛析構(gòu)函數(shù)的定義而已。(所以還是要養(yǎng)成好習(xí)慣)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容