單例模式即實現單例類,即系統中一個類只有一個實例,而且該實例易於外界訪問。這樣方便對實例個數進行控制並節約系統資源。
而單例常用與一些非局部靜態對象,對於這些對象,程序難以控制,對於這些存在與全局,且一般持久存在的對象,有時需要按照一定約束或順序來進行初始化,而初始化這些對象如果不使用單例方法的話會極度不安全。這個時候就要使用單例模式來解決這個問題。
實現單例的方法有很多,最簡單的一個是將對象放入函數中作為其靜態成員:
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;//類外的初始化。這樣就獲得一個比較完美的單例類了。