Singleton模式(單例模式)
要點:程序員
應用場景:
數據庫鏈接
打印機(能夠有幾個打印任務,但只能有一個打印機)
序列號生成(多個的話可能會導制重複)
數據庫表中記錄ID的生成(非單例的話可能會重複)數據庫
單例模式的特色:
單例類只能有一個實例。
單例類必須本身建立本身的惟一實例。
單例類必須給全部其它對象提供這一實例。
即:
一個私有構造函數——確保用戶沒法經過new直接實例它;
一個靜態私有成員變量instance;
一個靜態公有方法Instance()——方法負責檢驗並實例化本身,而後存儲在靜態成員變量中,以確保只有一個實例被建立。而且這也是一個全局訪問點。
例如:
class Singleton
{
public:
static Singleton *Instance(); // 單例操做
private:
Singleton();//關閉缺省構造函數
Singleton(const Singleton&);//關閉拷貝構造函數
Singleton& operator=(const Singleton&);//關閉賦值運算符
~Singleton();//避免被外界delete
static Singleton *m_Instance; // 單例指針
};
實質:
單例對象的類必須保證只有一個實例存在
單例模式應用:
每臺計算機能夠有若干個打印機,但只能有一個Printer Spooler,避免兩個打印做業同時輸出到打印機。
一個具備自動編號主鍵的表能夠有多個用戶同時使用,但數據庫中只能有一個地方分配下一個主鍵編號。不然會出現主鍵重複。
Singleton模式的結構:設計模式
注:Singleton模式包含的角色只有一個,就是Singleton。Singleton擁有一個私有構造函數,確保用戶沒法經過new直接實例它。除此以外,該模式中包含一個靜態私有成員變量instance與靜態公有方法Instance()。Instance方法負責檢驗並實例化本身,而後存儲在靜態成員變量中,以確保只有一個實例被建立。
—————————————————————————————————————————————————————————————————
(http://blog.csdn.net/benny5609/article/details/2438922)
Design Pattern無疑是每一個程序員都應該閱讀的一本書,這本書給出了23個Pattern,其中最簡單的就是Singleton Pattern了,這裏,我大概介紹一下本身使用Singleton時曾經用到過的一些作法,但願對你們有些幫助。
意圖
Single Pattern的主要是爲了保證類僅有一個實例,並保證提供一個訪問它的全局訪問點
適用狀況??
一個對象的行爲取決於它的狀態,而且它必須在運行時改變它的狀態
一個操做包含龐大的Switch&Case語句
優勢??
它將和特定狀態相關的行爲局部化了,這樣就能夠經過定義新的子類來增長新的狀態和轉換。
作法1:
由於在書中並無說起如何刪除m_pInstance指針,因此就提供exitInstance()方法手動刪除
class CSingleton
{
public:
virtual ~CSingleton();
static CSingleton* instance(void)
{
if(NULL == m_pInstance)
return m_pInstance = new CSingleton;
return m_pInstance;
}
void exitInstance(void)
{ delete m_pInstance;}
private
CSingleton();
static T* m_pInstance;
}
CSingleton* CSingleton::m_pInstance = NULL;
作法2:
在類中增長一個嵌套類,讓這個嵌套類負責刪除m_pInstance(相似於auto_ptr的原理)
class CSingleton
{
public:
virtual ~CSingleton();
static CSingleton* Instance()
{
if(NULL == m_pInstance)
m_pInstance = new CSingleton;
return m_pInstance;
}
private:
CSingleton();
static CSingleton* m_pInstance;
class Cleaner
{
public:
~Cleaner()
{ delete m_pInstance;}
}
friend class CSingleton::Cleaner;
static CSingleton::Cleaner cleaner;
}
CSingleton CSingleton::m_pInstance = NULL;
CSingleton::Cleaner CSingleton::cleaner;
作法3:
使用template的特性,建一個關於Singleton的template class,任何想使用Singleton Pattern 的類只須要從它這裏繼承就能夠了
template
class CSingleton
{
public:
static T
{
static T instance;
return &instance;
}
protected:
CSingleton(){};
virtual ~CSingleton(){};
private:
CSingleton(const CSingleton& source){};
};
若是類CTest想使用Singleton特性
CTest : public CSingleton
{
friend CSingleton
CTest();
public:
~CTest();
}
Singleton模式的C++實現研究(示例代碼)
Singleton模式面臨的兩個問題:
多線程中的單例模式
單例模式的銷燬
Singleton是一種通過改進的全局變量,該模式的描述很簡單,但實現卻很複雜。特別是Singleton對象的生命週期管理是實現Singleton時最傷腦筋的地方。
本文將討論如下幾個主題:
l 與單純的全局對象相比,Singleton的特性
l 用以支持單實例的C++基本手法
l 資源泄漏問題與Meyers Singleton
l Dead Reference問題
l Phoenix Singleton
l 帶壽命的Singleton
l 多線程問題
l 在動態庫中使用Singleton
1.1.1 靜態數據+靜態函數 != Singleton
Singleton好像能夠由靜態成員變量+靜態成員函數來取代?
例如:
Class Font{…};
Class PrintPort{…};
Class PrintJob {…};
Class MyOnlyPrinter
{
Public:
Static void AddPrintJob(PrintJob& newJob)
{
If(printQueue_.empty() && printingPort_.available())
{
printingPort_.send(newJob.Data());
}
Else
{
printQueue_.push(newJob);
}
}
Private:
Static queue
Static PrinterPort printingPort_;
Static Font defaultFont_;
}
PrintJob somePrintJob(「MyDocument.txt」);
MyOnlyPrinter::AddPrintJob(somePrintJob);
上述代碼有哪些缺點?
我認爲主要缺點有兩點:
n 代碼的初始化和清理可能會有麻煩。
n 初始化工做在程序一啓動時就已經完成,若是靜態函數到程序退出時都沒有調用,咱們的初始化工做就白作了。
1.1.2 用以支持Singleton的一些C++基本手法
單實例的原始版實現代碼:
class Singleton
{
public:
static Singleton *Instance(); // 單例操做
private:
Singleton();//關閉缺省構造函數
Singleton(const Singleton&);//關閉拷貝構造函數
Singleton& operator=(const Singleton&);//關閉賦值運算符
~Singleton();//避免被外界delete
static Singleton *m_Instance; // 單例指針
};
// 建立單例指針並初始化
Singleton *Singleton::m_Instance = NULL;
// 單例操做實現
Singleton *Singleton::Instance()
{
if (NULL == m_Instance)
{
m_Instance = new Singleton();
}
return m_Instance;
}
// 構造函數
Singleton::Singleton()
{
}
// 析構函數
Singleton::~Singleton()
{
}
經過上述代碼,Singleton對象的惟一性在編譯期就已經實現,這正是C++實現Singleton設計模式的精髓所在。
除了使用New操做符動態分配內存來實現Singleton,咱們還常常經過靜態變量實例方式來實現這個模式,下面給出了一種用靜態變量方式實現Singleton的代碼。
class Singleton
{
public:
static Singleton &Instance(); // 單例操做
private:
Singleton();//關閉缺省構造函數
Singleton(const Singleton&);//關閉拷貝構造函數
Singleton& operator=(const Singleton&);//關閉賦值運算符
~Singleton();//避免被外界delete private:
static Singleton m_Instance; // 靜態單例變量
};
// 建立單例
Singleton Singleton::m_Instance; 注意C++中靜態變量的初始化必須在類外面!
// 單例操做實現
Singleton &Singleton::Instance()
{
return m_Instance;
}
由於是類的靜態變量,上面的代碼在進程剛開始啓動時就實例化了Singleton對象。
這有兩個缺點:
n 初始化工做在程序一啓動時就已經完成,若是靜態函數到程序退出時都沒有調用,咱們的初始化工做就白作了;
n 初始化順序沒法肯定,這可能會遇到麻煩,例如:
Int global = Singleton::Instance()->DoSomething();
因爲沒法保證編譯器先初始化m_Instance,再初始化global全局變量,因此有可能在程序啓動時就發生異常。
所以,雖然上述實現簡單,但建議抵制住這種誘惑。
1.1.3 Meyers Singleton
對於上一節討論的指針實現方法,咱們有一個問題一直沒有討論,就是Singleton對象什麼時候析構的問題:Singleton對象應該在什麼時候摧毀自身實體?GoF著做中並無討論這個主題,事實上,這個問題確實很棘手。
其實,就算Singleton未被刪除,也不會形成內存泄露。此外,當一個進程終止時,全部現代操做系統都可以將進程所用到的內存徹底釋放。
然而,泄露可能仍是存在的,並且更隱蔽更有害,那就是資源泄漏。這是由於Singleton的構造函數能夠索求普遍的資源:網絡鏈接,OS的內核對象,IPC方法中的各類句柄,數據庫鏈接等。
避免資源泄漏的惟一正確方法是在程序關閉時期刪除Singleton對象。
對於靜態變量的初始化方法,有一個改進的版本,經過利用了編譯器的奇特技巧,能夠摧毀Singleton對象。
Singleton &Singleton::Instance()
{
static Singleton m_Instance; // 靜態單例變量
return m_Instance;
}
這一手法是由Scott Meyers(Meyers 1996a,條款26)最早提出,因此被稱爲Meryars singleton。函數內聲明的靜態變量函數內的static對象,在函數第一次被調用時初始化。
編譯器會產生一些代碼,保證在進程退出前析構函數會被調用。實際上在編譯器產生的代碼中會調用atexit函數,把單實例的析構函數註冊到atexit中,保證在進程退出前會調用這個析構函數。
Atexit函數內部維護了一個堆棧,先壓入棧的函數指針會後執行。函數
若是是靜態函數實現,則在程序推出時會調用靜態函數的析構方法嗎?
會!只要是靜態實例,在程序推出後會調用析構函數。優化
若是是New出來的實例,而沒有顯式調用析構函數呢在退出時會調用析構函數嗎?
不會!若是是new出來的實例變量,若是沒有顯示的調用析構函數,在程序退出時並不會調用析構函數。所以,若是單例模式以此方式實現,則會產生泄露(像數據庫鏈接、OS內核對像,網絡鏈接等可能不會被正確關閉)操作系統
咱們本身也能夠顯示的在代碼中調用atexit,如下是實現代碼。
class Singleton
{
…
private:
static void FreeInstance(void); // 釋放單例變量函數
};
// 單例操做實現
Singleton Singleton::Instance()
{
if (NULL == m_Instance)
{
m_Instance = new Singleton();
// 用系統函數atexit告訴系統在程序退出的時候調用FreeInstance
atexit(FreeInstance);
}
return m_Instance;
}
// 釋放單例變量函數
void Singleton::FreeInstance(void)
{
if (NULL != m_Instance)
{
delete m_Instance;
m_Instance = NULL;
}
}
1.1.4 Dead Reference問題
Meryers singleton是一種很好的Singleton的實現方式,大多數狀況下,咱們用這種方法實現Singleton就足夠了,但有些狀況下仍是會有問題。
舉個例子,假設有個程序使用了三個Singletons:Keyboard,Display和Log。咱們以Meryers singleton來實現這三個Singletons。假設Keyboard和Display先於Log實例化,那麼按照atexit後進先出的原則,在進程退出時Log應該先被析構,假設Log析構完成後,Keyboard析構函數調用時發生錯誤,這時須要調用Log來記日誌,但Log已經被析構了,因此程序會崩潰,這就是Dead Reference問題。
首先咱們看一個簡單的解決方案:
// Singleton.h
class Singleton
{
public:
Singleton& Instance()
{
if (!pInstance_)
{
// Check for dead reference
if (destroyed_)
{
OnDeadReference();
}
else
{
// First call—initialize
Create();
}
}
return pInstance_;
}
private:
// Create a new Singleton and store a
// pointer to it in pInstance_
static void Create();
{
// Task: initialize pInstance_
static Singleton theInstance;
pInstance_ = &theInstance;
}
// Gets called if dead reference detected
static void OnDeadReference()
{
throw std::runtime_error("Dead Reference Detected");
}
virtual ~Singleton()
{
pInstance_ = 0;
destroyed_ = true;
}
// Data
Singleton pInstance_;
bool destroyed_;
... disabled 'tors/operator= ...
};
// Singleton.cpp
Singleton Singleton::pInstance_ = 0;
bool Singleton::destroyed_ = false;
當出現Dead Reference問題時,程序會直接拋出C++異常,這個方案廉價,簡單而且不損失效率。
1.1.7 多線程問題
前面的實現代碼尚未考慮多線程問題,實際絕大部分狀況,程序都是運行在多線程環境下的。
對Singleton加鎖的代碼能夠像如下這樣的簡單方式:
Singleton& Singleton::Instance()
{
// mutex_ is a mutex object
// Lock manages the mutex
Lock guard(mutex_);
if (!pInstance_)
{
pInstance_ = new Singleton;
}
return *pInstance_;
}
這個好用,但效率不佳。
使用所謂的雙檢測鎖定技術能夠解決效率問題:
Singleton& Singleton::Instance()
{
if (!pInstance_) // 1
{
Guard myGuard(lock_); // 2
if (!pInstance_) // 3
{
pInstance_ = new Singleton; // 4
}
}
return *pInstance_;
}
即便使用雙檢測鎖定技術,在實踐中代碼也不老是正確。有些時候編譯器會對上述代碼進行優化,從而改變代碼執行順序,致使鎖定失效。
爲了不編譯器進行沒必要要的優化,須要在pInstance_聲明前添加volatile修飾詞。
1.1.8 在動態庫中使用Singleton
在動態庫中使用Singleton,與動態庫中的靜態變量的生命週期相關,通過實踐,能夠得出結論,咱們在前面討論的Singleton技術在動態庫下運行是沒有問題的。
在動態庫被卸載的時候,atexit的棧中保存的指針會被調用,能夠保證析構函數被執行。.net