在平時寫程式的時候往往會遇到這樣的需求,對於某些資源我們只想讓其只能由一個對象進行訪問,從而保證其完整性。比如,配置文件,工具類,線程池,緩存,日誌對象等。對這些資源進行訪問的對象我們只需要一個,當能對其進行讀寫的對象多了的時候就可能由於邏輯上的問題導致了很多意想不到的結果。在這個的背景下,結合了面...
在平時寫程式的時候往往會遇到這樣的需求,對於某些資源我們只想讓其只能由一個對象進行訪問,從而保證其完整性。比如,配置文件,工具類,線程池,緩存,日誌對象等。對這些資源進行訪問的對象我們只需要一個,當能對其進行讀寫的對象多了的時候就可能由於邏輯上的問題導致了很多意想不到的結果。在這個的背景下,結合了面向對象的思想,單例模式就出來了。
單例模式:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
通常我們可以讓一個全局變數使得一個對象被訪問,但它不能防止你實例化多個實例,一個好的辦法是讓類負責保存它的唯一實例。這個類可以保證沒有其他實例可以被創建。並且它可以提供一個訪問該實例的方法。
基於以上的需求和思想,將單例模式在C++上實現如下:
1 //Singleton.h頭文件 2 #pragma once 3 #ifndef SINGLETON_H 4 #define SINGLETON_H 5 /* 6 * author:caoshuping 7 * date:2016.03.14 8 * 說明:單例模式在C++中的實現 9 * 實現步驟: 10 * 1.將構造方法私有化,防止在類外進行實例化。 11 * 2.聲明靜態指針,指針指向Singleton,也就是指向唯一可以訪問對象的資源實例的指針 12 * 3.提供唯一的獲得實例的結構GetInstance() 13 * 類型:餓漢模式 and 懶漢模式 14 */ 15 class Singleton 16 { 17 private: 18 Singleton(); 19 static Singleton *m_pInstance; 20 public: 21 static Singleton * GetInstance(); 22 ~Singleton(); 23 }; 24 #endif 25 26 //Singleton.cpp實現文件 27 #include "Singleton.h" 28 #include <iostream> 29 30 using namespace std; 31 32 //類中的靜態成員變數必須在類外進行初始化,不然會報link error 33 Singleton *Singleton::m_pInstance = NULL; 34 //餓漢模式。也就是在類載入時候就對靜態成員變數進行初始化。 35 //Singleton *Singleton::m_pInstance = new Singleton(); 36 37 Singleton::Singleton() 38 { 39 m_pInstance = NULL; 40 } 41 Singleton::~Singleton() 42 { 43 } 44 /* 45 * 單例模式的唯一介面,使外部可以獲得唯一的指向資源的對象實例。 46 */ 47 Singleton *Singleton::GetInstance() 48 { 49 //判斷是否為空,為空的話進行初始化 50 //這種模式的單例模式為懶漢模式。也就是只有第一次需要使用這個對象實例的時候才對對象進行初始化 51 //當對靜態成員變數m_pInstance在類外直接調用new Singleton()進行對象初始化。這種為餓漢模式。可以看第8,9行代碼 52 if (m_pInstance == NULL) 53 m_pInstance = new Singleton(); 54 return m_pInstance; 55 }
單例類CSingleton有以下特征:
1.它有一個指向唯一實例的靜態指針m_pInstance,並且是私有的;
2.它有一個公有的函數,可以獲取這個唯一的實例,並且在需要的時候創建該實例;
3.它的構造函數是私有的,這樣就不能從別處創建該類的實例。
但是以上的實現還是有問題的。問題出現在指針。在C\C++中指針是柄雙刃劍,在帶來方便,實現一些magic的效果以外,也會帶來很多麻煩,最明顯的當屬記憶體泄露(memory leak)。對於以上的問題記憶體泄露的問題依然是存在。當然有人會說,由於m_pInstance是靜態變數,會保存在全局數據區,當程式運行結束的時候自然會釋放,即使我們在GetInstance中new了不顯示的delete也沒有關係。但是,以上只是一個簡單的實例。當我們想對訪問日誌對象設計單例模式的時候情況就不一樣了。由於日誌對象時一個文件,在對日誌文件進行訪問的時候就要保證一個文件句柄。而對文件的占用並不會隨著程式的結束而終止。所以此時必須要對文件句柄進行顯示的釋放。
怎麼在程式結束的時候對其釋放尼。基本的思路是定義一個內部類,然後再類內部定義一個內部類的對象,這樣在程式結束的時候,在釋放內部類對象的時候會調用內部類的析構函數,我們就可以將所有的釋放操作放在內部類的析構函數中進行。
所以單例模式的C++實現v2.0版本如下:
1 #pragma once 2 #ifndef SINGLETON_H 3 #define SINGLETON_H 4 #include<iostream> 5 using namespace std; 6 /* 7 * author:caoshuping 8 * date:2016.03.14 9 * 說明:單例模式在C++中的實現 10 * 實現步驟: 11 * 1.將構造方法私有化,防止在類外進行實例化。 12 * 2.聲明靜態指針,指針指向Singleton,也就是指向唯一可以訪問對象的資源實例的指針 13 * 3.提供唯一的獲得實例的結構GetInstance() 14 * 類型:餓漢模式 and 懶漢模式 15 */ 16 class Singleton 17 { 18 private: 19 Singleton(); 20 21 //垃圾回收類,這個類主要功能是在其析構函數中釋放m_pInstance所占用的資源 22 class CGarbo 23 { 24 public : 25 //在析構函數中釋放m_pInstance所占用的資源 26 ~CGarbo() 27 { 28 if (m_pInstance != NULL) 29 delete m_pInstance; 30 }; 31 }; 32 //在Singleton中定義CGarbo的靜態變數,這樣在程式結束的時候程式會銷毀程式中使用的靜態變數, 33 //在銷毀garbo對象的時候執行CGarbo類的析構函數,從而實現了對m_pInstance的釋放 34 static CGarbo garbo; 35 static Singleton *m_pInstance; 36 public: 37 static Singleton * GetInstance(); 38 ~Singleton(); 39 }; 40 #endif
cpp文件中內容沒有改變,主要改變在頭文件中。
類CGarbo被定義為CSingleton的私有內嵌類,以防該類被在其他地方濫用。
程式運行結束時,系統會調用CSingleton的靜態成員Garbo的析構函數,該析構函數會刪除單例的唯一實例。
使用這種方法釋放單例對象有以下特征:
1.在單例類內部定義專有的嵌套類;
2.在單例類內定義私有的專門用於釋放的靜態成員;
3.利用程式在結束時析構全局變數的特性,選擇最終的釋放時機;
4.使用單例的代碼不需要任何操作,不必關心對象的釋放。
好了,現在在單例模式在C++語言中的實現基本完成,在大多數情況下不會有什麼問題,其他的就是視具體情況來定了。那麼下麵就是要解決單例模式的線程安全問題。
在多線程的程式中,當多個線程調用GetInstance()函數時就有可能產生多個實例,也就new Singleton()會執行很多次。此時就違背了單例模式的最初設計思想了。所以在多線程的環境中我們需要進行加鎖操作。保證m_pInstance只有個實例。
我們可以對new Singleton進行加鎖操作,從而保證多線程時的安全。
C++實現如下:
1 //Lock.h文件 2 #pragma once 3 #ifndef CLOCK_H 4 #define CLOCK_H 5 #include<afxmt.h> 6 class CLock 7 { 8 private : 9 CCriticalSection m_cs; 10 public: 11 CLock(CCriticalSection cs); 12 ~CLock(); 13 }; 14 #endif 15 16 //Lock.cpp文件 17 #include "Lock.h" 18 CLock::CLock(CCriticalSection cs) :m_cs(cs) 19 { 20 m_cs.Lock(); 21 } 22 CLock::~CLock() 23 { 24 m_cs.Unlock(); 25 }
Singleton v3.0代碼如下:
1 //Singleton.h 2 #pragma once 3 #ifndef SINGLETON_H 4 #define SINGLETON_H 5 #include<iostream> 6 using namespace std; 7 /* 8 * author:caoshuping 9 * date:2016.03.14 10 * 說明:單例模式在C++中的實現 11 * 實現步驟: 12 * 1.將構造方法私有化,防止在類外進行實例化。 13 * 2.聲明靜態指針,指針指向Singleton,也就是指向唯一可以訪問對象的資源實例的指針 14 * 3.提供唯一的獲得實例的結構GetInstance() 15 * 類型:餓漢模式 and 懶漢模式 16 */ 17 class Singleton 18 { 19 private: 20 Singleton(); 21 22 //垃圾回收類,這個類主要功能是在其析構函數中釋放m_pInstance所占用的資源 23 class CGarbo 24 { 25 public : 26 //在析構函數中釋放m_pInstance所占用的資源 27 ~CGarbo() 28 { 29 if (m_pInstance != NULL) 30 delete m_pInstance; 31 }; 32 }; 33 //在Singleton中定義CGarbo的靜態變數,這樣在程式結束的時候程式會銷毀程式中使用的靜態變數, 34 //在銷毀garbo對象的時候執行CGarbo類的析構函數,從而實現了對m_pInstance的釋放 35 static CGarbo garbo; 36 static Singleton *m_pInstance; 37 //定義CCriticalSection對象,也就是鎖對象 38 static CCriticalSection cs; 39 public: 40 static Singleton * GetInstance(); 41 ~Singleton(); 42 }; 43 #endif 44 45 //Singleton.cpp 46 #include "Singleton.h" 47 #include <iostream> 48 #include "Lock.h" 49 using namespace std; 50 51 //類中的靜態成員變數必須在類外進行初始化,不然會報link error 52 Singleton *Singleton::m_pInstance = NULL; 53 //餓漢模式。也就是在類載入時候就對靜態成員變數進行初始化。 54 //Singleton *Singleton::m_pInstance = new Singleton(); 55 56 Singleton::Singleton() 57 { 58 m_pInstance = NULL; 59 } 60 61 62 Singleton::~Singleton() 63 { 64 } 65 66 /* 67 * 單例模式的唯一介面,使外部可以獲得唯一的指向資源的對象實例。 68 */ 69 Singleton *Singleton::GetInstance() 70 { 71 //判斷是否為空,為空的話進行初始化 72 //這種模式的單例模式為懶漢模式。也就是只有第一次需要使用這個對象實例的時候才對對象進行初始化 73 //當對靜態成員變數m_pInstance在類外直接調用new Singleton()進行對象初始化。這種為餓漢模式。可以看第8,9行代碼 74 //雙重判斷,如果不加第一層的if判斷,則每次在調用GetInstance時都要建立CLock對象,這樣會降低效率,而加了則可以避免。 75 if (m_pInstance == NULL) 76 { 77 //建立鎖CLock對象 78 CLock lock(cs); 79 //臨界區 80 if (m_pInstance == NULL) 81 m_pInstance = new Singleton(); 82 //臨界區 83 84 //註意解鎖的情況,由於解鎖lock是局部變數,所以在函數結束後會自動銷毀,在自動銷毀的時候會執行析構函數進行解鎖 85 } 86 87 88 return m_pInstance; 89 }
完整的代碼可以在如下的連接中下載:http://pan.baidu.com/s/1boeSCXp