1. 程式人生 > >關於C++單例模式記憶體釋放問題的一點點總結

關於C++單例模式記憶體釋放問題的一點點總結

目錄

寫在最前面

  網路上有很多關於C++單例模式的帖子,其中不乏精品之作。本篇文字在吸收了精華之餘,僅作了個人的一點點總結。
  通過new出一個物件來實現的單例,不論單例是通過餓漢方式,還是懶漢方式來實現,都面臨一個問題,即new出來的物件由誰釋放,何時釋放,怎麼釋放 ?簡單的實現可以參考C++ 單例模式
  如果物件沒有被釋放,在執行期間可能會存在記憶體洩露問題。有人可能會說,在程序結束時,作業系統會進行必要的清理工作,包括釋放程序的所有堆疊等資訊,即使存在記憶體洩露,作業系統也會收回的;且對於單例來講,程序執行期間僅有一個物件例項,而且該例項有可能根本就沒有進行記憶體的申請操作,不釋放例項所佔記憶體,對程序的執行也不會造成影響。這麼說好像很有道理的樣子,既然作業系統會清理一切後續工作,那麼我們還有必要進行記憶體釋放工作嗎?
  

正文

  閒話少敘,言歸正傳。作為一個非著名的程式猿,我和大多數同類一樣,對程式碼有著不一般的情結,且有強迫症。成對的使用new和delete是程式猿們最基礎的素養。這麼看來,單例物件的釋放必須要在程式碼中體現出來。

方式一:由程式猿在程式結束之前,通過呼叫delete來釋放

很自然的,可能會有部分猿/媴們[其實就是我啦^-^]想到,把釋放工作交給解構函式來處理不就行了。想法是不錯,程式碼要怎麼寫吶?可能如下:

~dtor()
{
   delete instance;
}

可惜的是,一:new出來的物件,必須用與之對應的delete顯示的來釋放,程式並不會自動呼叫解構函式來析構new出來的物件;二:在delete的時候會呼叫解構函式,解構函式中又呼叫了delete,然後又呼叫了解構函式……這樣就進入了一個無限的迴圈之中。

可能的程式碼:

int main(int argc, char ** argv)
{
    //...

    delete Singleton::get_instance();
    //...
}

valgrind檢測結果
所有記憶體都被釋放了
通過valgrind工具,我們可以看到,所有記憶體都被釋放了。這種處理完成了任務,好像無可厚非。但是,大多數情況下,這條語句會被遺忘,如果程式中存在多個單例,也很容易將某個物件的釋放操作遺漏。

方式二:通過C標準庫的atexit()函式註冊釋放函式

  atexit()函式可以用來註冊終止函式。如果打算在main()結束後執行某些操作,可以使用該函式來註冊相關函式。

可能的程式碼:

void del_singleton_01()
{
    if (Singleton::get_instance())
    {
        delete Singleton::get_instance();
    }
}

int main(int argc, char **argv)
{
    // ...
    atexit(del_singleton_01);
    // ...
}

valgrind檢測結果
所有記憶體都被釋放了
  標準規定atexit()至少可以註冊32個終止函式,如果系統中有多個單例,我們可能要註冊多個函式,或者在同一個終止函式中釋放所有單例物件。但是方式一中的問題依然存在。必須由程式猿/媴手工註冊,且有可能遺漏某個物件。

方式三:由單例類提供釋放介面

  本方式由單例類提供一個釋放物件的函式,在該函式內部進行物件的釋放操作。其本質與方式一併無太大差別,同樣的繼承了方式一的缺點。
可能的程式碼:

class Singleton {
public:
    // ...
    void del_object() {
        if (instance) {
            delete instance;
            instance = 0;
        }
    }
    // ...
};

int main(int argc, char ** argv)
{
    // ...
    Singleton::get_instance()->del_object();
    // ...
}

  雖然這是一種可行的釋放物件方式,但是這種方式並沒有明顯的優點。這不是我們想要的方案。

方式四:讓作業系統自動釋放

  我們知道,程序結束時,靜態物件的生命週期隨之結束,其解構函式會被呼叫來釋放物件。因此,我們可以利用這一特性,在單例類中宣告一個內嵌類,該類的解構函式專門用來釋放new出來的單例物件,並宣告一個該類型別的static物件。

可能的程式碼:

class Singleton {
public:
    // ...
private:
    // ...
    static Singleton * instance;
    class GarbageCollector {
    public:
        ~GarbageCollector() {
            if (Singleton::instance) {
                delete Singleton::instance;
                Singleton::instance = 0;
            }
        }
    };
    static GarbageCollector gc;
};

// 定義
Singleton::GarbargeCollector Singleton::gc;
// ...

valgrind檢測結果:
所有記憶體都被釋放了
好了,我們可以像之前一樣使用單例了,不需要再關心物件的釋放問題。程序結束時,作業系統會幫我們去釋放的。

寫在最後面

  之所以要進行記憶體的釋放,是因為在單例的實現過程中,我們使用了new來建立物件。如果在實現過程中,不使用new,而是使用靜態[區域性]物件的方式,就不存在釋放物件的問題了。
可能的關鍵程式碼:

class Singleton {
    // ...
    static Singleton instance;
    // ...
};

// ...
Singleton Singleton::instance;
// ...

或者

class Singleton {
public:
    Singleton & get_instance();
    // ...
};

Singleton & Singleton::get_instance()
{
    static Singleton instance;
    return instance;
}