單例模式
-
- 一、單例模式思想
-
- 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;
}