C++的三種單例模式-----深度解析
三種單例模式轉自部落格:http://blog.csdn.net/q_l_s/article/details/52369065
小編想要對三種的單例模式做下解析
簡介
因為在設計或開發中,肯定會有這麼一種情況,一個類只能有一個物件被建立,如果有多個物件的話,可能會導致狀態的混亂和不一致。這種情況下,單例模式是最恰當的解決辦法。它有很多種實現方式,各自的特性不相同,使用的情形也不相同。今天要實現的是常用的三種,分別是餓漢式、懶漢式和多執行緒式。
通過單例模式, 可以做到:
1. 確保一個類只有一個例項被建立
2. 提供了一個對物件的全域性訪問指標
3. 在不影響單例類的客戶端的情況下允許將來有多個例項
懶漢式
懶漢式的特點是延遲載入,比如配置檔案,採用懶漢式的方法,顧名思義,懶漢麼,很懶的,配置檔案的例項直到用到的時候才會載入。。。。。。
GetInstance()使用懶惰初始化,也就是說它的返回值是當這個函式首次被訪問時被建立的。這是一種防彈設計——所有GetInstance()之後的呼叫都返回相同例項的指標:class CSingleton { public: static CSingleton* GetInstance() { if ( m_pInstance == NULL ) m_pInstance = new CSingleton(); return m_pInstance; } private: CSingleton(){}; static CSingleton * m_pInstance; };
CSingleton* p1 = CSingleton :: GetInstance();
CSingleton* p2 = p1->GetInstance();
CSingleton & ref = * CSingleton :: GetInstance();
對GetInstance稍加修改,這個設計模板便可以適用於可變多例項情況,如一個類允許最多五個例項。
餓漢式
餓漢式的特點是一開始就載入了,如果說懶漢式是“時間換空間”,那麼餓漢式就是“空間換時間”,因為一開始就建立了例項,所以每次用到的之後直接返回就好了。
class CSingleton
{
private:
CSingleton()
{
}
public:
static CSingleton * GetInstance()
{
static CSingleton instance;
return &instance;
}
};
在餓漢式的單例類中,其實有兩個狀態,單例未初始化和單例已經初始化。假設單例還未初始化,有兩個執行緒同時呼叫GetInstance方法,這時執行 m_pInstance == NULL 肯定為真,然後兩個執行緒都初始化一個單例,最後得到的指標並不是指向同一個地方,不滿足單例類的定義了,所以餓漢式的寫法會出現執行緒安全的問題!在多執行緒環境下,要對其進行修改。
多執行緒下的單例模式
這裡要處理的是懶漢模式。
class Singleton
{
private:
static Singleton* m_instance;
Singleton(){}
public:
static Singleton* getInstance();
};
Singleton* Singleton::getInstance()
{
if(NULL == m_instance)
{
Lock();//借用其它類來實現,如boost
if(NULL == m_instance)
{
m_instance = new Singleton;
}
UnLock();
}
return m_instance;
}
使用double-check來保證thread safety.但是如果處理大量資料時,該鎖才成為嚴重的效能瓶頸。
下面對文章中提到的進行下簡單的總結和分析:
1.深入的理解下懶漢和惡漢
其實就是看定義的事靜態成員物件變數還是靜態成員物件指標變數,因為如果定義了靜態成員物件變數,程式在執行之初已經分配了空間,就要呼叫構造函數了,而你在呼叫getinstance的時候,不會再呼叫夠贊函數了,因為之前已經呼叫過了,你就是用的現成的,就是所謂的惡漢模式,上來先把吃的準備好了,因為餓怕了,怕後期準備會捱餓
而定義了靜態成員物件指標變數,程式執行之初也會分配空間,但是那個是指標的空間,而不是物件的空間,所以不會呼叫物件的建構函式,而只有呼叫getinstance進行new操作的時候,才會對其呼叫建構函式,就是現上轎現扎耳朵眼,比較懶惰,所以叫懶漢模式。
這裡我寫了兩個小例子,大家看到例子就更清楚了。在3中進行展示
2.文中有一處書寫錯誤。
在餓漢式的單例類中,其實有兩個狀態,單例未初始化和單例已經初始化。假設單例還未初始化,有兩個執行緒同時呼叫GetInstance方法,這時執行 m_pInstance == NULL 肯定為真,然後兩個執行緒都初始化一個單例,最後得到的指標並不是指向同一個地方,不滿足單例類的定義了,所以餓漢式的寫法會出現執行緒安全的問題!在多執行緒環境下,要對其進行修改。
-----這裡的惡漢應該改成懶漢
3.樓主寫的惡漢程式碼有點問題,正常的程式碼應該是下面這樣,和樓主的區別在於(樓主的程式碼是執行緒不安全的,因為靜態的區域性變數是在呼叫的時候分配到靜態儲存區,所以在編譯的時候沒有分配)
class CMsBsGPSInfoStart
{
public:
static CMsBsGPSInfoStart& GetInstance();
protected:
CMsBsGPSInfoStart();
~CMsBsGPSInfoStart();
private:
static CMsBsGPSInfoStart _instance;
private:
//CLock m_lkMsBsGPSInfoStartFlag;
bool m_bMsBsGPSInfoStartFlag; //
public:
bool GetMsBsGPSInfoStart();
bool SetMsBsGPSInfoStart(bool bIsStart);
};
CMsBsGPSInfoStart CMsBsGPSInfoStart::_instance;
CMsBsGPSInfoStart::CMsBsGPSInfoStart() : m_bMsBsGPSInfoStartFlag(false)
{
std::cout << "enter CMsBsGPSInfoStart::CMsBsGPSInfoStart() " << endl;
}
CMsBsGPSInfoStart::~CMsBsGPSInfoStart()
{
std::cout << "enter CMsBsGPSInfoStart::~CMsBsGPSInfoStart() " << endl;
}
CMsBsGPSInfoStart& CMsBsGPSInfoStart::GetInstance()
{
std::cout << "CMsBsGPSInfoStart::GetInstance()" << endl;
return _instance;
}
bool CMsBsGPSInfoStart::SetMsBsGPSInfoStart(bool bIsStart)
{
m_bMsBsGPSInfoStartFlag = bIsStart;
return true;
}
我在主函式中包含標頭檔案 instancetest.h後,主函式裡什麼也沒有做
#include"instancetest.h"
using namespace::std;
int main()
{
return 0;
}
直接生成exe進行除錯
除錯結果是:
這就證明在沒有呼叫Getinstance的時候已經執行了建構函式。
惡漢模式的測試程式碼如下,大致程式碼如下:
class sun :public son
{
public:
~sun()
{
std::cout << "sun::~sun()";
}
public:
static sun * GetInstance();
private:
sun(int a, int b, int c, double m, int d);
static sun * m_instance;
};
#include"instance.h"
sun * sun::m_instance(NULL);
sun::sun(int a, int b, int c, double m, int d) :son(a, b, c, m, d)
{
std::cout << "sun::sun"<<endl;
}
sun * sun::GetInstance()
{
if (NULL == m_instance)
{
m_instance = new sun(5,6,7,8.2,10);
}
return m_instance;
}
然後主函式如下:
#include"instance.h"
//#include"instancetest.h"
using namespace::std;
int main()
{
sun * test = sun::GetInstance();
return 0;
}
執行結果如下所示:
這就是懶漢模式,用到的時候,才去呼叫,然後才會進行建構函式的呼叫,該模式下是執行緒不安全的。
4.對於樓主寫的惡漢模式有問題的原因具體解釋如下:
靜態區域性物件:在程式執行到該物件的定義處時,建立物件並呼叫相應的建構函式!
如果在定義物件時沒有提供初始指,則會暗中呼叫預設建構函式,如果沒有預設建構函式,則自動初始化為0。
如果在定義物件時提供了初始值,則會暗中呼叫型別匹配的帶參的建構函式(包括拷貝建構函式),如果沒有定義這樣的建構函式,編譯器可能報錯!
直到main()結束後才會呼叫解構函式!
作者:撒哈拉的水草
連結:https://www.zhihu.com/question/40693991/answer/87843670
來源:知乎
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。