【前言】最近看公司的代碼,好多項目里面使用了單例模式。不會涉及公司具體代碼,僅對單例模式做一個學習總結。
一、C++單例模式
通過單例模式可以保證系統中只有一個類只有一個實例,並提供一個訪問它的全局訪問點,該實例被所有程序模塊共享。對於系統中的某些類來說,只有一個實例很重要,比如一個打印機可以有多個打印任務,但是只有一個正在工作的任務,一個系統只能有一個窗口管理器或文件系統。
單例模式的要點有三個:1. 單例類只能有一個實例 2. 它必須自行創建這個實例 3. 它必須自行向整個系統提供提供這個實例。從具體實現角度來說,就是以下三點:1. 單例模式的類只提供私有的構造函數 2. 類定義中含有一個該類的靜態私有對象 3. 該類提供了一個靜態的公有的函數用於創建或獲取它本身的靜態私有對象
單例模式的一些注意點:
- 實例控制: 單例模式會阻止其他對象實例化自己的單例對象的副本,從而確保所有對象都訪問唯一實例;
- 靈活性: 因為類控制實例化過程,所以類可以靈活更改實例化過程;
- 開銷: 雖然數量很少,但如果每次對象請求引用時都要檢查是否存在類的實例,將仍然需要一些開銷,這個問題可以通過靜態初始化解決此問題:定義一個私有的靜態指針m_sInstance,和一個公有的靜態函數 GetInstance()。
單例模式又分為餓漢式單例和懶漢式單例,餓漢式單例在單例類被加載時就實例化一個對象交給自己的引用;而懶漢式在調用取得實例方法的時候才會實例化對象。
單例模式的優點:
- 在內存中只有一個對象,節省內存空間
- 避免頻繁的創建銷毀對象,可以提高性能
- 避免對共享資源的多重占用
- 可以全局訪問
單例模式的適用場景:
- 需要頻繁實例化然后銷毀的對象
- 創建對象耗時過多或者耗資源過多,但又經常用到的對象
- 有狀態的工具類對象
- 頻繁訪問數據庫或文件的對象
- 以及其他要求只有一個對象的場景
在C++ 中一般都使用懶漢式單例,但懶漢式單例可能會有線程安全,異常安全問題。實例代碼如下:
class CSingleton { private: CSingleton() //構造函數是私有的 { } public: static CSingleton * GetInstance() { static CSingleton *m_pInstance; if(m_pInstance == NULL) //判斷是否第一次調用 m_pInstance = new CSingleton(); return m_pInstance; } };
對於拷貝構造和分配操作符如果我們不打算自定義的話,那么最好將它們也置為私有且不實現
考慮到線程安全和異常安全,實現代碼如下:
class Lock { private: CCriticalSection m_cs; public: Lock(CCriticalSection cs) : m_cs(cs) { m_cs.Lock(); } ~Lock() { m_cs.Unlock(); } }; class CSingleton { private: CSingleton(); CSingleton(const CSingleton &); CSingleton& operator = (const CSingleton &); public: static CSingleton *Getinstance(); static CSingleton *m_Instance; static CCriticalSection cs; }; CSingleton* CSingleton::m_Instance = 0; CSingleton* CSingleton::Getinstance() { if(m_Instance == NULL) { //double check Lock lock(cs); //用lock實現線程安全,用資源管理類,實現異常安全 //使用資源管理類,在拋出異常的時候,資源管理類對象會被析構,析構總是發生的無論是因為異常拋出還是語句塊結束。 if(m_Instance == NULL) { m_Instance = new Singleton(); } } return m_Instance; }
之所以在Instantialize函數里面對pInstance 是否為空做了兩次判斷,因為該方法調用一次就產生了對象,pInstance == NULL 大部分情況下都為false,如果按照原來的方法,每次獲取實例都需要加鎖,效率太低。而改進的方法只需要在第一次 調用的時候加鎖,可大大提高效率。