1. 程式人生 > >設計模式的C++實現 1.單例模式

設計模式的C++實現 1.單例模式

單例模式即實現單例類,即系統中一個類只有一個例項,而且該例項易於外界訪問。這樣方便對例項個數進行控制並節約系統資源。

而單例常用與一些非區域性靜態物件,對於這些物件,程式難以控制,對於這些存在與全域性,且一般持久存在的物件,有時需要按照一定約束或順序來進行初始化,而初始化這些物件如果不使用單例方法的話會極度不安全。這個時候就要使用單例模式來解決這個問題。

實現單例的方法有很多,最簡單的一個是將物件放入函式中作為其靜態成員:

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;//類外的初始化。
這樣就獲得一個比較完美的單例類了。