吳中區(qū)企業(yè)網(wǎng)站制作哪家靠譜seo常用工具網(wǎng)站
一、概述
? ? ? ? 單例模式也稱單態(tài)模式,是一種創(chuàng)建型模式,用于創(chuàng)建只能產(chǎn)生一個(gè)對象實(shí)例的類。例如,項(xiàng)目中只存在一個(gè)聲音管理系統(tǒng)、一個(gè)配置系統(tǒng)、一個(gè)文件管理系統(tǒng)、一個(gè)日志系統(tǒng)等,甚至如果吧整個(gè)Windows操作系統(tǒng)看成一個(gè)項(xiàng)目,那么其中只存在一個(gè)任務(wù)管理器窗口等。引入單例模式的實(shí)現(xiàn)意圖:保證一個(gè)類僅有一個(gè)實(shí)例存在,同時(shí)提供能對該實(shí)例訪問的全局方法。
二、單例模式分類
1、懶漢模式
1)代碼示例
class CSingletonImpl
{
public:
?? ?static CSingletonImpl* GetInstance()
?? ?{
?? ??? ?if (m_pInstance == nullptr)
?? ??? ?{
?? ??? ??? ?m_pInstance = new CSingletonImpl;
?? ??? ?}
?? ??? ?return m_pInstance;
?? ?}
private:
?? ?CSingletonImpl(){};
?? ?~CSingletonImpl(){};
?? ?CSingletonImpl(const CSingletonImpl& the);
?? ?CSingletonImpl& operator=(const CSingletonImpl& other);
private:
?? ?static CSingletonImpl* m_pInstance;
};
CSingletonImpl*CSingletonImpl::m_pInstance = nullptr;
2)說明
單例模式為了防止多對象問題,將構(gòu)造函數(shù),析構(gòu)函數(shù),拷貝構(gòu)造函數(shù),賦值運(yùn)算符函數(shù)設(shè)置為私有,同時(shí)設(shè)置公有唯一接口方法來創(chuàng)建對象,同時(shí)定義類靜態(tài)指針。這是通用方法,那么會(huì)有什么問題呢?如果在單一線程中使用則沒什么問題,但是在多線程中使用則可能導(dǎo)致問題,如果多個(gè)線程可能會(huì)因?yàn)椴僮飨到y(tǒng)時(shí)間片調(diào)度問題切換造成多對象產(chǎn)生,那么解決這個(gè)問題的方案就是對GetInstance()成員函數(shù)枷鎖。
示例代碼:
加入私有成員變量:static std::mutex m_mutex;
static CSingletonImpl* GetInstance()
{
?m_mutex.lock();
?if (m_pInstance == nullptr)
?{
? m_pInstance = new CSingletonImpl;
?}
?m_mutex.unlock();
?return m_pInstance;
}
加入以上代碼沒有問題了嗎?呵呵,還不行,雖然對接口函數(shù)加鎖,從代碼邏輯上沒有問題,實(shí)現(xiàn)了線程安全,但是從執(zhí)行效率上來說,是有大問題的。當(dāng)程序運(yùn)行中GetInstance()可能會(huì)被多個(gè)線程頻繁調(diào)用,每次調(diào)用都會(huì)經(jīng)歷加鎖解鎖的過程,這樣的話會(huì)嚴(yán)重影響程序執(zhí)行效率,而且加鎖機(jī)制僅僅對第一次創(chuàng)建對象有意義,對象一旦創(chuàng)建則變成只讀對象,在多線程中,對只讀對象的訪問加鎖不僅代價(jià)大,而且無意義。那么如何解決這個(gè)問題呢?那就是雙重鎖定機(jī)制,基于這種機(jī)制函數(shù)實(shí)現(xiàn)代碼:
?? ?static CSingletonImpl* GetInstance()
?? ?{
?? ??? ?if (m_pInstance == nullptr)
?? ??? ?{
?? ??? ??? ?std::lock_guard<std::mutex> siguard(si_mutex);
?? ??? ??? ?if (m_pInstance == nullptr)
?? ??? ??? ?{
?? ??? ??? ??? ?m_pInstance = new CSingletonImpl;
?? ??? ??? ?}
?? ??? ?}
?? ??? ?return m_pInstance;
?? ?}
上述雙重鎖定機(jī)制看起來比較完美,但實(shí)際上存在潛在的問題,內(nèi)存訪問重新排序?qū)е码p重鎖定失效的問題,比較推薦的方法時(shí)C++11新標(biāo)準(zhǔn)的一些特性,示例代碼如下:
#include <mutex>
#include <atomic>
//通過原子變量解決雙重鎖定底層問題(load,store)
class CSingletonImpl
{
public:
?? ?static CSingletonImpl* GetInstance()
?? ?{
?? ??? ?CSingletonImpl* task = m_taskQ.load(std::memory_order_relaxed);?
?? ??? ?std::atomic_thread_fence(std::memory_order_acquire);
?? ??? ?if (task == nullptr)
?? ??? ?{
?? ??? ??? ?std::lock_guard<std::m_mutex> lock(m_mutex);
?? ??? ??? ?task = m_taskQ.load(std::memory_order_relaxed);?
?? ??? ??? ?if (task == nullptr)
?? ??? ??? ?{
?? ??? ??? ??? ?task = new CSingletonImpl;
?? ??? ??? ??? ?std::atomic_thread_fence(std::memory_order_release);
?? ??? ??? ??? ?m_taskQ.store(task, std::memory_order_relaxed);
?? ??? ??? ?}
?? ??? ?}
?? ??? ?return task;
?? ?}
private:
?? ?CSingletonImpl(){};
?? ?~CSingletonImpl(){};
?? ?CSingletonImpl(const CSingletonImpl& the);
?? ?CSingletonImpl& operator=(const CSingletonImpl& other);
private:
?? ?static std::mutex m_mutex;
?? ?static std::atomic<CSingletonImpl*> m_taskQ;
};
std::mutex CSingletonImpl::m_mutex;
std::atomic<CSingletonImpl*> CSingletonImpl::m_taskQ;
2、餓漢模式
1)示例代碼
class CSingletonImpl
{
public:
?? ?static CSingletonImpl* GetInstance()
?? ?{
?? ??? ?return m_pInstance;
?? ?}
private:
?? ?CSingletonImpl(){};
?? ?~CSingletonImpl(){};
?? ?CSingletonImpl(const CSingletonImpl& the);
?? ?CSingletonImpl& operator=(const CSingletonImpl& other);
private:
?? ?static CSingletonImpl* m_pInstance;
};
CSingletonImpl*CSingletonImpl::m_pInstance = new CSingletonImpl();
2)說明
此類模式可稱為餓漢式--------程序一執(zhí)行不管是否調(diào)用了GetInstance()成員函數(shù),這個(gè)單例類對象就已經(jīng)被創(chuàng)建了。在餓漢式單例類代碼的實(shí)現(xiàn)必須要注意,如果一個(gè)項(xiàng)目中有多個(gè).cpp源文件,而且這些源文件中包含對全局變量的初始化代碼,例如某個(gè).cpp中可能存在如下代碼:
int g_test =?CSingletonImpl::GetInstance()->m_i; //m_i是int類型變量
那么這樣的代碼是不安全的,因?yàn)槎鄠€(gè)源文件中全局變量的初始化順序是不確定的,很可能造成GetInstance()函數(shù)返回是nullptr,此時(shí)去訪問m_i成員變量肯定會(huì)導(dǎo)致程序執(zhí)行異常。所以,對餓漢式單例類對象的使用,應(yīng)該在程序入口函數(shù)開始執(zhí)行后,例如main函數(shù)后。
注意:函數(shù)第一次執(zhí)行時(shí)被初始化的靜態(tài)變量與通過編譯器常量進(jìn)行初始化的基本類型靜態(tài)變量這兩種情況,不要再單例類的析構(gòu)函數(shù)中引用其他單例類對象。