面向對象編程(C++篇3)——析構

[toc]

1. 概述

類的析構函數執行與構造函數相反的操作,當對象結束其生命週期,程序就會自動執行析構函數:

class ImageEx
{
public:
    ImageEx()
    {
        cout << "Execute the constructor!" << endl;
    }

    ~ImageEx()
    {
        cout << "Execute the destructor!" << endl;
    }
};

int main()
{
    ImageEx imageEx;
    return 0;
}

那麼同樣的問題來了,爲什麼要有析構函數呢?

2. 詳論

2.1. 對象生命週期

在經典C++中,需要通過new/delete來手動管理動態內存。如果我們在類中申請一個動態數組,並且通過自定義的函數Release()來釋放它:

class ImageEx
{
public:
    ImageEx()
    {
        cout << "Execute the constructor!" << endl;
        data = new unsigned char[10];
    }

    ~ImageEx()
    {
        cout << "Execute the destructor!" << endl;
    }

    void Release()
    {
        delete[] data;
        data = nullptr;
    }

private:
    unsigned char * data;
};

int main()
{
    {
        ImageEx imageEx;
        imageEx.Release();
    }

    return 0;
}

那麼,當類對象離開作用域,結束生命週期之前,就必須顯示調用一次成員函數Release(),否則就會造成內存泄漏:對象在調用析構函數之後,只會銷燬數據成員data本身,而不是其指向的內存。

那麼,一個合理的實現是,將成員函數Release()放入到析構函數:

class ImageEx
{
public:
    ImageEx()
    {
        cout << "Execute the constructor!" << endl;
        data = new unsigned char[10];
    }

    ~ImageEx()
    {
        Release();
        cout << "Execute the destructor!" << endl;
    }

private:
    unsigned char * data;

    void Release()
    {
        delete[] data;
        data = nullptr;
    }
};

int main()
{
    {
        ImageEx imageEx;       
    }

    return 0;
}

這樣,當類對象離開作用域,結束生命週期之前,就自動通過析構函數,實現了動態數組的釋放。好處是顯而易見的:實現了類似於內置數據類型對象的生命週期管理,我們可以像使用內置數據類型對象一樣使用類對象。這也體現了前文《面向對象編程(C++篇1)——引言》中提到的設計原則:類是抽象的自定義數據類型。

2.2. 不一定需要顯式析構

在一些現代高級編程語言(C#、Java、Javascript)中,已經不用去手動管理動態內存,取而代之的,是其與操作系統的中間件(.net,jvm,瀏覽器)的GC(垃圾回收)機制。而在現代C++中,提倡通過智能指針(stdshared_ptr、stdunique_ptr、stdweak_ptr)來管理動態內存;對於動態數組,則使用標準容器stdvector則更好。在兩者的內部都實現了前文提到的對象生命週期管理,在離開作用域後,通過析構函數自動釋放管理的內存,無需再手動進行回收。

那麼,一個顯而易見的推論就出來了,如果我們在類中使用智能指針或者vector容器來替代new/delete管理動態內存,是不是就可以不用析構函數了?嚴格來說,是不用顯式使用析構函數:

class ImageEx
{
public:
    ImageEx():
        data(10)
    {
        cout << "Execute the constructor!" << endl;        
    }

private:
    std::vector<unsigned char> data;
};

int main()
{
    ImageEx imageEx;      

    return 0;
}

實際上,並不是這個類不存在析構函數,而是編譯器會爲它生成一個合成的析構函數,在這個析構函數體中,什麼也不用做。因爲類中的動態內存,已經交由stdvector容器來管理。當類對象離開作用域調用析構函數之後,會銷燬這個stdvector容器數據成員,進而觸發其析構函數,釋放其管理的內存。

2.3. 析構的必要性

根據上一節內容,不一定需要顯式析構。因爲現代C的一些機制能夠幫你自動管理動態內存。但是析構函數還是必要的,這是由於C語言本身的性質決定的。作爲C語言大部分內容的超集,需要兼容C語言手動管理內存的特性。更重要的是,現代操作系統幾乎全部由C語言編寫,與底層的交互不可避免的需要手動使用動態內存管理。

3. 總結

所以我們就能理解了,C++這門語言的設計哲學就是就是這樣:既想要C語言的高性能,也想要高級語言高度抽象的特性。如果我們必須兼容C語言底層設計,那我們最好使用析構函數釋放動態內存;否則多數情況下,我們應該使用智能指針或者stl容器來管理動態內存,從而避免顯示使用析構函數。

上一篇 目錄 下一篇

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章