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