天天看點

【C++】設計模式之單例模式(懶漢模式和餓漢模式)

單例模式

    • 一、單例模式思想
      • 1、核心
      • 2、分析
      • 3、執行個體設計
    • 二、懶漢模式
      • 1、不借助執行個體的懶漢模式(不完善)
      • 2、存在的問題
      • 3、改進後的懶漢模式架構
    • 三、餓漢模式
      • 4、不借助執行個體的餓漢模式

一、單例模式思想

1、核心

讓一個類中隻能生成一個對象,也就是如果使用者調用時是不能生成第二個對象的。

2、分析

在類中生成一個對象,那我們隻能在生成對象上進行操作。

(1)對象生成的特點:

  • 開辟記憶體空間

    這是系統進行開辟的,我們無法進行操作

  • 調用構造函數

    我們可以讓構造函數隻被調用一次,也就是讓對象隻能在第一次調用時,調用構造函數。

    (2)讓構造函數隻被調用一次的方法:

    給出一個判斷的辨別:

  • 如果已經有對象,直接傳回之前的對象。
  • 如果沒有對象生成,就調用構造函數生成唯一的對象。

    (3)一般來說,構造函數是在public(公有)下的,那在任意位置都能調用,是以無法控制生成對象的個數。

    (4)單例模式把構造函數寫在private(私有)下,也就是把構造函數屏蔽起來,不讓任意函數調用。

    (5)那構造函數屏蔽起來,那怎麼在調用點生成唯一的對象???

    給出一個接口,用接口來生成唯一的對象。

    也可以了解為在公有下寫一個函數來生成唯一的對象。

    (6)公有下的接口滿足:

  • 不依賴對象,我們知道對象的生成是不能依賴對象的,是以要設定為靜态的。
  • 傳回值不能用類類型,因為類類型會生成臨時變量,違背我們生成唯一對象的初衷。是以一般要用*或者&,不會生成臨時對象。

3、執行個體設計

class Master
{
public:
	static Master* getInstance(char* name, int age, bool sex)//(1)
	{
		if (pm == NULL)
		{
			pm = new Master(name, age, sex);
		}
		return pm;
	}
private:
	Master(char* name, int age, bool sex)
		:mname(new char[strlen(name) + 1]()), mage(age), msex(sex)
	{
		strcpy_s(mname, strlen(name) + 1, name);
		cout << "mname:" << mname << "  mage:" << mage << "  msex:" << msex << endl;
	}
	char* mname;
	int mage;
	bool msex;
	static Master* pm;//(2)
};
Master* Master::pm = NULL;//(3)
int main()
{
	Master* m1 = Master::getInstance("Zhangsan", 50, true);//(4)
	Master* m2 = Master::getInstance("Lisi", 60, true);
	Master* m3 = Master::getInstance("Wangwu", 40, true);
	return 0;
}
           

代碼解析:

(1)

static Master* getInstance(char* name, int age, bool sex)

對象的生成是不依賴對象的,是以要使用靜态;傳回值類型不能使用類類型,防止生成臨時變量。

(2)

static Master* pm;

靜态的成員變量與對象無關,所有對象可以共享,這樣就可以在公有處調用成員變量pm,但必須在類外進行初始化。

(3)

Master* Master::pm = NULL;

在類外進行初始化。

(4)

Master* m1 = Master::getInstance("Zhangsan", 50, true);

不能再用

Master m1

的方式擷取對象,因為構造函數在私有下了。

二、懶漢模式

延時加載,使用對象時才生成對象是線程不安全的單例模式

1、不借助執行個體的懶漢模式(不完善)

根據上述思想分析所得架構:

class SingleTon
{
public:
	static SingleTon* getInstance( )
	{
		if (psing == NULL)
		{
			psing = new SingleTon( );
		}
		return psing;
	}
private:
	SingleTon()
	{
	}
	static SingleTon* psing;
};
SingleTon* SingleTon::psing = NULL;
int main()
{
	SingleTon* psingle1 = SingleTon::getInstance( );
	SingleTon* psingle2 = SingleTon::getInstance( );
	SingleTon* psingle3 = SingleTon::getInstance( );
	return 0;
}
           

2、存在的問題

(1)如果調用點寫一個

SingleTon single = *psingle1;

,這會調用拷貝構造函數,我們隻是屏蔽了構造函數,但是系統會生成預設的拷貝構造函數,是以,要在私有下寫

singleTon(const SingleTon&);

(2)屏蔽拷貝構造函數時隻寫聲明即可,因為在單例模式下隻會生成一個對象,調用構造函數,是不會調用拷貝構造函數的,是以隻寫聲明放在私有下,目的是告訴編譯器拷貝構造函數在私有下,外部不能通路。

(3)上面的架構是線程不安全的單例模式,會存在以下問題:

(參考Linux線程)

  • 線程會開辟多餘的對象
  • 記憶體洩漏
  • 可能會使資料出錯

    是以要加上lock和unlock上鎖,也就是當A線程時間到了後,B線程也無法在進入

    psing = new SingleTon( );

    這一語句,就不會生成多餘的對象。

    (2)上述上鎖後還會有一個問題:要是唯一的對象已經生成後,再去調用

    getInstance

    ,系統會繼續加鎖、解鎖,産生額外的開銷,影響效率,是以對其進行改進。判斷如果唯一對象已經産生,就不再進行加鎖解鎖操作。

3、改進後的懶漢模式架構

class SingleTon
{
public:
	static SingleTon* getInstance( )
	{
		if (psing == NULL)
		{
			m_mtx.lock();
			if (psing == NULL)
			{
				psing = new SingleTon();
			}
			m_mtx.unlock();
		}
		return psing;
	}
private:
	SingleTon()
	{
	}
	SingleTon(const SingleTon& );//改進1,屏蔽拷貝構造函數
	static SingleTon* psing;
	static mutex m_mtx;//互斥鎖
};
SingleTon* SingleTon::psing = NULL;
mutex SingleTon::m_mtx;//改進2
int main()
{
	SingleTon* psingle1 = SingleTon::getInstance( );
	SingleTon* psingle2 = SingleTon::getInstance( );
	SingleTon* psingle3 = SingleTon::getInstance( );
	return 0;
}
           

三、餓漢模式

貪婪加載,提前生成對象,是線程安全的單例模式。

1、線程是隸屬于程序的執行路徑。即線程開啟是在程序開啟之後。

2、**如果在分線程之前把唯一的對象生成好,就能屏蔽掉線程不安全的情況。**也不用用互斥鎖。

3、缺點:如果後面不在有對象的調用,但是對象已經生成,就會造成字眼浪費。

4、不借助執行個體的餓漢模式

class SingleTon
{
public:
	static SingleTon* getInstance( )
	{
		return psing;
	}
private:
	SingleTon()
	{
	}
	SingleTon(const SingleTon& );
	static SingleTon* psing;
};
SingleTon* SingleTon::psing = new SingleTon();//main函數執行前
int main()
{
	SingleTon* psingle1 = SingleTon::getInstance( );
	SingleTon* psingle2 = SingleTon::getInstance( );
	SingleTon* psingle3 = SingleTon::getInstance( );
	return 0;
}
           

繼續閱讀