單例模式,也叫單子模式,是一種經常使用的軟件設計模式。在應用這個模式時,單例對象的類必須保證只有一個實例存在。許多時候整個系統只須要擁有一個的全局對象,這樣有利於咱們協調系統總體的行爲。好比在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,而後服務進程中的其餘對象再經過這個單例對象獲取這些配置信息。這種方式簡化了在複雜環境下的配置管理。html
實現單例模式的思路是:一個類能返回對象一個引用(永遠是同一個)和一個得到該實例的方法(必須是靜態方法,一般使用getInstance這個名稱);當咱們調用這個方法時,若是類持有的引用不爲空就返回這個引用,若是類保持的引用爲空就建立該類的實例並將實例的引用賦予該類保持的引用;同時咱們還將該類的構造函數定義爲私有方法,這樣其餘處的代碼就沒法經過調用該類的構造函數來實例化該類的對象,只有經過該類提供的靜態方法來獲得該類的惟一實例。c++
上面是引用維基百科上的定義。 設計模式
既然單例模式只容許存在一個對象,那麼對象的拷貝,賦值就都是不容許的,所以把拷貝構造函數、賦值操做符所有聲明爲private安全
第一種實現服務器
class Singleton { private: static Singleton *m_instance; Singleton(){}//隱藏構造函數 Singleton(const Singleton &){}//隱藏拷貝構造函數 Singleton& operator=(const Singleton &a){}//隱藏賦值操做符 ~Singleton(){}//隱藏析構函數 public: static Singleton *getInstance() { if(m_instance == NULL) m_instance = new Singleton; return m_instance; } }; Singleton * Singleton::m_instance = NULL;
這種實現很明顯知足單例模式的要求,可是有兩個問題 (1)咱們new的對象沒有被delete,(2)這種設計不是線程安全的(兩個線程可能同時判斷m_instance == NULL,這樣就有兩個實例被建立了),爲了解決上面的問題,第二種實現以下併發
第二種實現函數
class Singleton { private: static Singleton *m_instance; Singleton(){}//隱藏構造函數 Singleton(const Singleton &){}//隱藏拷貝構造函數 Singleton& operator=(const Singleton &a){}//隱藏賦值操做符 ~Singleton(){}//隱藏析構函數 class DelInstance { public: ~DelInstance() { if(Singleton::m_instance) delete Singleton::m_instance; } }; static DelInstance delIns; //負責回收new出來的對象; public: static Singleton *getInstance() { if(m_instance == NULL) { lock(); //加鎖(lock 和 unlock是隨便寫的函數,c++自己沒有) if(m_instance == NULL) m_instance = new Singleton; unlock();//釋放鎖 } return m_instance; } }; Singleton::DelInstance Singleton::delIns; Singleton * Singleton::m_instance = NULL;
這裏咱們使用了另外一個私有內嵌類DelInstance,在Singleton內定義了一個靜態的的對象delIns來負責回收new出來的對象,由於靜態對象delIns在程序結束時會自動調用本身析構函數從而釋放m_instance指向的內存,這裏新手可能會犯兩個錯誤:線程
一、不使用額外的類,直接把delete語句寫在singleton的析構函數中。這種作法是錯誤的,由於咱們是經過new出來的singleton實例,程序結束時不會自動調用析構函數,再者若是真的調用了就會進入一個無限循環的狀態,即singleton的析構函數是爲了刪除一個singleton對象,刪除該對象時,又會調用singleton的析構函數,這樣一直循環下去。設計
二、delIns成員不定義成static。這也是錯誤的,若是delIns不是static,那麼delIns就要等到singleton對象析構時纔會析構,可是delIns的目的就是要析構singleton,這就矛盾了。若是delIns是static的,他的生命期和他所在的類對象沒有關係,他至關因而全局的,當他的生命期到的時候就會自動析構,從而析構singleton。c++11
getInstance中咱們使用了double-check來保證線程安全,只有當m_instance是NULL時,線程纔會加鎖。這樣也保證了只建立了一個對象實例。
這是一種懶漢模式,即等到須要時才建立對象的實例。 本文地址
第三種實現
class Singleton { private: static Singleton *m_instance; Singleton(){}//隱藏構造函數 Singleton(const Singleton &){}//隱藏拷貝構造函數 Singleton& operator=(const Singleton &a){}//隱藏賦值操做符 ~Singleton(){}//隱藏析構函數 class DelInstance { public: ~DelInstance() { if(Singleton::m_instance) delete Singleton::m_instance; } }; static DelInstance delIns; //負責回收new出來的對象; public: static Singleton *getInstance() { return m_instance; } }; Singleton::DelInstance Singleton::delIns; Singleton * Singleton::m_instance = new Singleton;
這是屬於餓漢模式,即一開始就建立一個類的實例,之後都返回其地址,是線程安全的。
第四種實現
class Singleton { private: static Singleton s; Singleton(){}//隱藏構造函數 Singleton(const Singleton &){}//隱藏拷貝構造函數 Singleton& operator=(const Singleton &a){}//隱藏賦值操做符 ~Singleton(){}//隱藏析構函數 public: static Singleton *getInstance() { return &s; } }; Singleton Singleton::s;
餓漢模式,這種實現定義一個私有的靜態對象實例,(注意不能在類中定義自身的非靜態對象,由於這樣會造成無限循環定義,而靜態對象由於保證只有一個副本,所以不會循環定義),這也是線程安全的
第五種實現
class Singleton { private: Singleton(){}//隱藏構造函數 Singleton(const Singleton &){}//隱藏拷貝構造函數 Singleton& operator=(const Singleton &a){}//隱藏賦值操做符 ~Singleton(){}//隱藏析構函數 public: static Singleton *getInstance() { lock();//c++11 能夠不用加鎖 static Singleton s; unlock(); return &s; } };
懶漢模式,這種實現只有當第一次調用getInstance時定義局部靜態變量s,之後直接返回。c++11以前須要加鎖,由於變量的初始化操做不是原子操做,不加鎖就不是線程安全的的;c++11則不用加鎖,由於c++11保證:若是指令邏輯進入一個未被初始化的聲明變量,全部併發執行應當等待完成該變量完成初始化(見here)。
注意
對於第1、2、3、四種方式,都有可能致使static initialization order fiasco (可參考here)。由於c++中,在全局或名字空間範圍內的全局變量,在一個類中被聲明爲static,或,在一個文件範圍被定義爲static。這三種變量統稱「非局部靜態對象」,這三種對象的初始化順序「C++」未做明確規定。所以若是有個類在構造函數中調用了getInstance:
struct Foo { Foo() { Singleton::getInstance(); } }; Foo foo;
不能保證foo初始化時,m_instance已經初始化
【版權聲明】轉載請註明出處:http://www.cnblogs.com/TenosDoIt/p/3639395.html