話題二:指針
*
與引用&
的區(qū)別
單例模式
單例模式(Singleton Pattern)是設(shè)計(jì)模式最簡單的形式之一玻蝌,其目的是使得一個(gè)對象成為系統(tǒng)中的唯一實(shí)例澡屡。
這種設(shè)計(jì)模式及到一個(gè)單一的類蔬充,該類負(fù)責(zé)創(chuàng)建自己的對象,同時(shí)確保只有單個(gè)對象被創(chuàng)建。這個(gè)類提供了一種訪問其唯一對象的方式歧沪,可以直接訪問,需要實(shí)例化該類的對象莲组。
單例模式的三大要點(diǎn):
- 單例類有且僅有一個(gè)實(shí)例
- 單例類必須自行創(chuàng)建自己的唯一實(shí)例
- 單例類必須給所有其他對象提供這一實(shí)例
實(shí)現(xiàn)角度分為三點(diǎn):
- 提供一個(gè)private構(gòu)造函數(shù)(防止外部調(diào)用而構(gòu)造類的實(shí)例)
- 提供一個(gè)該類的static private對象
- 提供一個(gè)static public函數(shù)诊胞,用于創(chuàng)建獲取其本身的靜態(tài)私有對象(例如:GetInstance())
除此之外還有一些關(guān)鍵點(diǎn)
- 線程安全(雙檢鎖-DCL,即:double-checked locking)
- 資源釋放
局部靜態(tài)變量
這種方式很常見锹杈,實(shí)現(xiàn)非常簡單撵孤,而且無需擔(dān)心單例的銷毀問題。
// singleton.h#ifndef SINGLETON_H#define SINGLETON_H// 非真正意義上的單例class Singleton{public: static Singleton& GetInstance() { static Singleton instance; return instance; }private: Singleton() {}};#endif // SINGLETON_H
但是嬉橙,這并非真正意義上的單例早直。當(dāng)使用如下方式訪問單例時(shí):
Singleton single = Singleton::GetInstance();
這會(huì)出現(xiàn)了一個(gè)類拷貝問題,從而違背了單例的特性市框。產(chǎn)生這個(gè)問題原因在于:編譯器會(huì)生成一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù)霞扬,來支持類的拷貝。
為了避免這個(gè)問題枫振,有兩種解決方式:
- 將 GetInstance() 函數(shù)的返回類型修改為指針喻圃,而非引用。
- 顯式地聲明類的拷貝構(gòu)造函數(shù)粪滤,并重載賦值運(yùn)算符斧拍。
對于第一種方式,只需要修改 GetInstance() 的返回類型即可:
// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H
// 單例
class Singleton
{
public:
// 修改返回類型為指針類型
static Singleton* GetInstance()
{
static Singleton instance;
return &instance;
}
private:
Singleton() {}
};#endif // SINGLETON_H
既然編譯器會(huì)生成一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù)杖小,那么肆汹,為什么不讓編譯器不這么干呢愚墓?這就產(chǎn)生了第二種方式:
// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H
#include <iostream>
using namespace std;
// 單例
class Singleton
{
public:
static Singleton& GetInstance()
{
static Singleton instance;
return instance;
}
void doSomething()
{
cout << "Do something" << endl;
}
private:
Singleton() {} // 構(gòu)造函數(shù)(被保護(hù))
Singleton(Singleton const &); // 無需實(shí)現(xiàn)
Singleton& operator = (const Singleton &); // 無需實(shí)現(xiàn)};
#endif // SINGLETON_H
這樣以來,既可以保證只存在一個(gè)實(shí)例昂勉,又不用考慮內(nèi)存回收的問題浪册。
Singleton::GetInstance().doSomething(); // OK
Singleton single = Singleton::GetInstance(); // Error 不能編譯通過
懶漢式/餓漢式
在講解之前,先看看 Singleton 的頭文件(懶漢式/餓漢式公用):
// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H
// 單例 - 懶漢式/餓漢式公用
class Singleton
{
public:
static Singleton* GetInstance();
private:
Singleton() {} // 構(gòu)造函數(shù)(被保護(hù))
private:
static Singleton *m_pSingleton; // 指向單例對象的指針
};
#endif // SINGLETON_H
懶漢式的特點(diǎn):
- Lazy 初始化
- 非多線程安全
優(yōu)點(diǎn):第一次調(diào)用才初始化岗照,避免內(nèi)存浪費(fèi)村象。
缺點(diǎn):必須加鎖(在“線程安全”部分分享如何加鎖)才能保證單例,但加鎖會(huì)影響效率攒至。
// singleton.cpp
#include "singleton.h"
// 單例 - 懶漢式
Singleton *Singleton::m_pSingleton = NULL;
Singleton *Singleton::GetInstance()
{
if (m_pSingleton == NULL)
m_pSingleton = new Singleton();
return m_pSingleton;
}
餓漢式的特點(diǎn):
- 非 Lazy 初始化
- 多線程安全
優(yōu)點(diǎn):沒有加鎖厚者,執(zhí)行效率會(huì)提高。缺點(diǎn):類加載時(shí)就初始化迫吐,浪費(fèi)內(nèi)存库菲。
// singleton.cpp
#include "singleton.h"
// 單例 - 餓漢式
Singleton *Singleton::m_pSingleton = new Singleton();
Singleton *Singleton::GetInstance()
{
return m_pSingleton;
}
線程安全
在懶漢式下,如果使用多線程渠抹,會(huì)出現(xiàn)線程安全隱患蝙昙。為了解決這個(gè)問題,我們引入了雙檢鎖 - DCL 機(jī)制梧却。
// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H
#include <iostream>
#include <mutex>
using namespace std;
// 單例 - 懶漢式/餓漢式公用
class Singleton
{
public:
static Singleton* GetInstance();
private:
Singleton() {} // 構(gòu)造函數(shù)(被保護(hù))
private:
static Singleton *m_pSingleton; // 指向單例對象的指針
static mutex m_mutex; // 鎖
};
#endif // SINGLETON_H
// singleton.cpp
#include "singleton.h"
// 單例 - 懶漢式(雙檢鎖 DCL 機(jī)制)
Singleton *Singleton::m_pSingleton = NULL;
mutex Singleton::m_mutex;
Singleton *Singleton::GetInstance()
{
if (m_pSingleton == NULL) {
std::lock_guard<std::mutex> lock(m_mutex); // 自解鎖
if (m_pSingleton == NULL) {
m_pSingleton = new Singleton();
}
}
return m_pSingleton;
}
這樣奇颠,就可以保證線程安全了,但是放航,會(huì)帶來較小的性能影響烈拒。
資源釋放
有內(nèi)存申請,就要有對應(yīng)的釋放广鳍,可以采用下述兩種方式:
- 主動(dòng)釋放(手動(dòng)調(diào)用接口來釋放資源)
- 自動(dòng)釋放(由程序自己釋放)
要手動(dòng)釋放資源荆几,添加一個(gè) static 接口,編寫需要釋放資源的代碼:
// 單例 - 主動(dòng)釋放static void DestoryInstance(){ if (m_pSingleton != NULL) { delete m_pSingleton; m_pSingleton = NULL; }}
然后在需要釋放的時(shí)候赊时,手動(dòng)調(diào)用該接口:
Singleton::GetInstance()->DestoryInstance();
方式雖然簡單吨铸,但很多時(shí)候,容易忘記調(diào)用 destoryInstance()祖秒。這時(shí)诞吱,可以采用更方便的方式:
// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H
#include <iostream>
using namespace std;
// 單例 - 自動(dòng)釋放
class Singleton
{
public:
static Singleton* GetInstance();
private:
Singleton() {} // 構(gòu)造函數(shù)(被保護(hù))
private:
static Singleton *m_pSingleton; // 指向單例對象的指針
// GC 機(jī)制
class GC
{
public:
~GC()
{
// 可以在這里銷毀所有的資源,例如:db 連接竭缝、文件句柄等
if (m_pSingleton != NULL) {
cout << "Here destroy the m_pSingleton..." << endl;
delete m_pSingleton;
m_pSingleton = NULL;
}
}
static GC gc; // 用于釋放單例
};
};
#endif // SINGLETON_H
只需要聲明 Singleton::GC 即可:
// main.cpp
#include "singleton.h"
Singleton::GC Singleton::GC::gc; // 重要
int main()
{
Singleton *pSingleton1 = Singleton::GetInstance();
Singleton *pSingleton2 = Singleton::GetInstance();
cout << (pSingleton1 == pSingleton2) << endl;
return 0;
}
在程序運(yùn)行結(jié)束時(shí)房维,系統(tǒng)會(huì)調(diào)用 Singleton 的靜態(tài)成員 GC 的析構(gòu)函數(shù),該析構(gòu)函數(shù)會(huì)進(jìn)行資源的釋放抬纸。這種方式的最大優(yōu)點(diǎn)就是在“不知不覺”中進(jìn)行咙俩,所以,對我們來說湿故,尤為省心阿趁。