單例模式即實現單例類,即系統中一個類只有一個執行個體,而且該執行個體易於外界訪問。這樣方便對執行個體個數進行控制並節約系統資源。
而單例常用與一些非局部靜態對象,對於這些對象,程式難以控制,對於這些存在與全域,且一般持久存在的對象,有時需要按照一定約束或順序來進行初始化,而初始化這些對象如果不使用單例方法的話會極度不安全。這個時候就要使用單例模式來解決這個問題。
實現單例的方法有很多,最簡單的一個是將對象放入函數中作為其靜態成員:
class SingleTon;SingleTon* getSingleTonInstance(){static SingleTon* instance = new SingleTon(); return instance;}class SingleTon{friend SingleTon* getSingleTonInstance();private:SingleTon(){}};這是我認為的最簡單的實現單例模式的方法,不足的地方在於這個獲得單例對象的函數不在類內。首先要實現單例模式,將建構函式聲明為稀有,這樣建構函式就不能被方法,也不能隨意建立單例類的對象。而這裡獲得執行個體為函數的靜態對象,所以其只有一個,且存在時間為建立到程式結束。
當然,也可以將函數中的靜態對象改為類中的靜態對象,而將這個全域的函數設定為類中的靜態函數,這樣就得到一個更加普遍常用的形式:
class SingleTon{public:static SingleTon* getInstance(){static SingleTon* instance = new SingleTon();return instance;}~SingleTon(){}private:SingleTon(){}SingleTon(const SingleTon&);SingleTon& operator=(const SingleTon&);};這裡還是使用了函數中的靜態成員,使用類中的靜態成員也是可以的:
class SingleTon{public:static SingleTon* getInstance(){if(NULL == instance)instance = new SingleTon();return instance;}private:SingleTon(){}SingleTon(const SingleTon&);SingleTon& operator=(const SingleTon&);static SingleTon* instance;};SingleTon* SingleTon::instance;// = new SingleTon();類內的靜態成員初始化可以調用類中的私人的建構函式。
為了安全性,這裡將複製建構函式和賦值操作符都給隱藏了,但是解構函式還是可見的,程式員還是會誤用delete來刪除這個單例實體,這樣是不安全的,可以選擇將解構函式放入私人中,隱藏解構函式,對於一些對象在最後結束時析構,則不用關心其釋放過程。
但是如果在程式執行中要調用解構函式進行執行個體的刪除的話,就使用一個公有的函數來封裝解構函式,且將解構函式置為私人:
class SingleTon{public:static SingleTon* getInstance(){if(NULL == instance)instance = new SingleTon();return instance;}static void delelteInstance(){if(NULL != instance){delete instance;instance = NULL;}}private:SingleTon(){}SingleTon(const SingleTon&);SingleTon& operator=(const SingleTon&);static SingleTon* instance;~SingleTon();};SingleTon* SingleTon::instance ;
這裡就已經基本上在單線程上安全了,然後就考慮多線程,當多個線程企圖同時初始化 單例執行個體時,就出現了問題,要使用互斥來解決問題,這裡就使用臨界區來解決:
CRITICAL_SECTION cs;class SingleTon{public:static SingleTon* getInstance(){if(NULL == instance){EnterCriticalSection(&cs); if(NULL == instance){//雙檢鎖,在進入臨界區後再檢測一次是否對象已經建立instance = new SingleTon();}LeaveCriticalSection(&cs); }return instance;}static void delelteInstance(){if(NULL != instance){EnterCriticalSection(&cs);if(NULL != instance){delete instance;instance = NULL;}LeaveCriticalSection(&cs); }}private:SingleTon(){}SingleTon(const SingleTon&);SingleTon& operator=(const SingleTon&);static SingleTon* instance;~SingleTon();};SingleTon* SingleTon::instance ;這裡使用雙檢鎖的機制,第一次是判斷是否需要對執行個體進行操作,第二次是在進入臨界區即對資料加鎖後,判斷在資料已經不會再被外界幹擾的情況下,第一次判斷和第二次判斷之間是否被其他線程進行了操作,這樣兩次判斷保證了執行個體的安全。
但是這樣還是不夠安全,因為多線程中還是會有一些特殊情況,在類中一些檔案被鎖了,如檔案控制代碼,資料庫連接等,這些隨著程式的關閉並不會立即關閉資源,必須要在程式關閉前,進行手動釋放。這裡的指不會自動關閉,是對於解構函式是私人的情況下,由於系統無法訪問私人的解構函式,對於沒有這些串連時,即類只在記憶體中佔據了一些地址,則系統將其視為全域變數,在結束時釋放其所在記憶體資源,所以沒有記憶體流失。而若類中有檔案控制代碼和資料庫連接這些東西,系統並不會幫忙關閉這些,所以必須手動的調用解構函式中對這些檔案的關閉操作。
對於這樣的情況,一般會使用一種私人內嵌類Garbo,意為垃圾工人,在單例類中包含一個私人的靜態垃圾工人對象,當程式結束時,系統會調用這個對象的解構函式,而這個解構函式中對單例類對象實現析構。
CRITICAL_SECTION cs;class SingleTon{public:static SingleTon* getInstance(){if(NULL == instance){EnterCriticalSection(&cs); if(NULL == instance){//雙檢鎖,在進入臨界區後再檢測一次是否對象已經建立instance = new SingleTon();}LeaveCriticalSection(&cs); }return instance;}static void delelteInstance(){if(NULL != instance){EnterCriticalSection(&cs);if(NULL != instance){delete instance;instance = NULL;}LeaveCriticalSection(&cs); }}private:SingleTon(){}SingleTon(const SingleTon&);SingleTon& operator=(const SingleTon&);static SingleTon* instance;~SingleTon(){}//相應的關閉串連等操作class GarBo{public:~GarBo(){if(NULL != instance){EnterCriticalSection(&cs);if(NULL != instance){delete instance;instance = NULL;}LeaveCriticalSection(&cs); }}};static GarBo gc ;};SingleTon* SingleTon::instance ;SingleTon::GarBo SingleTon::gc;//類外的初始化。這樣就獲得一個比較完美的單例類了。