1. 程式人生 > >C++的三種單例模式-----深度解析

C++的三種單例模式-----深度解析

三種單例模式轉自部落格:http://blog.csdn.net/q_l_s/article/details/52369065

小編想要對三種的單例模式做下解析

簡介

        因為在設計或開發中,肯定會有這麼一種情況,一個類只能有一個物件被建立,如果有多個物件的話,可能會導致狀態的混亂和不一致。這種情況下,單例模式是最恰當的解決辦法。它有很多種實現方式,各自的特性不相同,使用的情形也不相同。今天要實現的是常用的三種,分別是餓漢式、懶漢式和多執行緒式。

        通過單例模式, 可以做到:

1. 確保一個類只有一個例項被建立 
2. 提供了一個對物件的全域性訪問指標 
3. 在不影響單例類的客戶端的情況下允許將來有多個例項


懶漢式

      懶漢式的特點是延遲載入,比如配置檔案,採用懶漢式的方法,顧名思義,懶漢麼,很懶的,配置檔案的例項直到用到的時候才會載入。。。。。。

class CSingleton  
{  
public:  
static CSingleton* GetInstance()  
{  
     if ( m_pInstance == NULL )    
         m_pInstance = new CSingleton();  
     return m_pInstance;  
}  
private:  
    CSingleton(){};  
    static CSingleton * m_pInstance;  
};
GetInstance()使用懶惰初始化,也就是說它的返回值是當這個函式首次被訪問時被建立的。這是一種防彈設計——所有GetInstance()之後的呼叫都返回相同例項的指標:
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
來源:知乎
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。