單例模式可能是使用最廣泛的設計模式,其意圖是保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
單例模式有很多實現方法,在C++中,甚至可以直接用一個全局變量做到這一點,但是這樣的代碼顯得很不優雅。《設計模式》一書中給出了一種很不錯的實現,定義一個單例類,使用類的私有靜態指針變量指向類的唯一實例,並用一個公有的靜態方法獲得該實例。
class CSingleton { private: CSingleton() //構造函數是私有的 { } static CSingleton *m_pInstance; public: static CSingleton * GetInstance() { if(m_pInstance == NULL) //判斷是否第一次調用 m_pInstance = new CSingleton(); return m_pInstance; } };
用戶訪問唯一實例的方法只有GetInstance()成員函數,如果不通過這個函數,任何創建實例的嘗試都會失敗,因為類的構造函數是私有的。GetInstance()使用懶惰初始化,也就是說它的返回值只有在這個函數首次被訪問時被創建。這是一種防彈設計——所有GetInstance()之后的調用都返回相同的實例指針:
CSingleton* p1 = CSingleton :: GetInstance();
CSingleton* p2 = p1->GetInstance();
CSingleton & ref = * CSingleton :: GetInstance();
對GetInstance稍加修改,這個設計模板便可以適用於可變多實例情況,如一個類允許最多五個實例。
有經驗的讀者可能會問,m_pInstance指向的空間什么時候釋放?更嚴重的問題是,該實例的析構函數什么時候調用?
可以在程序結束時調用GetInstance(),並對返回的指針調用delete操作。這樣這可以實現功能,但不僅丑陋,而且容易出錯。
一個妥善的辦法是讓這個類自己知道在合適的時候自己刪除,或者說把刪除自己的操作掛在操作系統某個合適的點上。我們知道,程序在結束的時候,系統會析構所有的全局變量。事實上,系統也會析構所有類的靜態成員變量,就像這些靜態成員也是全局變量一樣。利用這個特性,我們可以在單例類中定義這樣一個靜態成員變量,而它的唯一工作就是在析構函數中刪除單例類的實例。如下面代碼中的CGarbo類。
class CSingleton { private: CSingleton() { } static CSingleton *m_pInstance; class CGarbo //它的唯一工作就是在析構函數中刪除CSingleton的實例 { public: ~CGarbo() { if(CSingleton::m_pInstance) delete CSingleton::m_pInstance; } }; static CGarbo Garbo; //定義一個靜態成員變量,程序結束時,系統會自動調用它的析構函數 public: static CSingleton * GetInstance() { if(m_pInstance == NULL) //判斷是否第一次調用 m_pInstance = new CSingleton(); return m_pInstance; } };
類CGarbo被定義為CSingleton的私有嵌套類,以防止該類在其他地方被濫用。程序運行結束時,系統會調用CSingleton的靜態成員Garbo的析構函數,該析構函數會刪除單例的唯一實例。
進一步討論,
但是添加一個類的靜態對象,總是讓人不太滿意,所以有人用如下方法來重新實現單例和解決它相應的問題,代碼如下:
class CSingleton { private: CSingleton() //構造函數是私有的 { } public: static CSingleton & GetInstance() { static CSingleton instance; //局部靜態變量 return instance; } };
使用局部靜態變量,非常強大的方法,完全實現了單例的特性,而且代碼量更少,也不用擔心單例銷毀問題。
但使用此種方法也會出現問題,當如下方法使用單例時問題來了:
Singleton singleton = Singleton :: GetInstance();
這么做就出現了一個類拷貝的問題,這就違背了單例的特性。產生這個問題原因在於:編譯器會為類生成一個默認的構造函數,來支持類的拷貝。
最后沒有辦法,我們要禁止類拷貝和類賦值,禁止程序員用這種方法來使用單例,比如返回一個指針,
class CSingleton { private: CSingleton() //構造函數是私有的 { } public: static CSingleton * GetInstance() { static CSingleton instance; //局部靜態變量 return &instance; } };
可以直接讓編譯器不這么干嗎?這時我才想起可以顯示的聲明類拷貝的構造函數,和重載 = 操作符,新的單例類如下:
class CSingleton { private: CSingleton() //構造函數是私有的 { } CSingleton(const CSingleton &); CSingleton & operator = (const CSingleton &); public: static CSingleton & GetInstance() { static CSingleton instance; //局部靜態變量 return instance; } };
關於Singleton(const Singleton &);和 Singleton & operate = (const Singleton&);函數,需要聲明成私有的,並且只聲明不實現。這樣,如果用上面的方式來使用單例時,不管是在友元類中還是其他的,編譯器都是報錯。
考慮到線程安全、異常安全,可以做以下擴展:
class Lock { private: CCriticalSection m_cs; public: Lock(CCriticalSection cs) : m_cs(cs) { m_cs.Lock(); } ~Lock() { m_cs.Unlock(); } }; class Singleton { private: Singleton(); Singleton(const Singleton &); Singleton& operator = (const Singleton &); public: static Singleton *Instantialize(); static Singleton *pInstance; static CCriticalSection cs; }; Singleton* Singleton::pInstance = 0; Singleton* Singleton::Instantialize() { if(pInstance == NULL) { //double check Lock lock(cs); //用lock實現線程安全,用資源管理類,實現異常安全 //使用資源管理類,在拋出異常的時候,資源管理類對象會被析構,析構總是發生的無論是因為異常拋出還是語句塊結束。 if(pInstance == NULL) { pInstance = new Singleton(); } } return pInstance; }
之所以在Instantialize函數里面對pInstance 是否為空做了兩次判斷,因為該方法調用一次就產生了對象,pInstance == NULL 大部分情況下都為false,如果按照原來的方法,每次獲取實例都需要加鎖,效率太低。而改進的方法只需要在第一次 調用的時候加鎖,可大大提高效率。
————————————————
版權聲明:本文為CSDN博主「hackbuteer1」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/Hackbuteer1/article/details/7460019