十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營維護(hù)+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
設(shè)計(jì)模式描述了對(duì)象如何進(jìn)行通信才能不牽涉相互的數(shù)據(jù)模型和方法。 保持這種獨(dú)立性一直是一個(gè)好的面向?qū)ο蟪绦蛟O(shè)計(jì)的目標(biāo)。 Gang of Four的“Design Patterns: Elements of Resualbel Software”書將設(shè)計(jì)模式 歸納為三大類型,共23種。 創(chuàng)建型模式 : 通常和對(duì)象的創(chuàng)建有關(guān),涉及到對(duì)象實(shí)例化的方式。(共5種模式) 行為型模式: 通常和對(duì)象間通信有關(guān)。(共11種模式) 結(jié)構(gòu)型模式: 描述的是如何組合類和對(duì)象以獲得更大的結(jié)構(gòu)。(共7種模式) 類模式描述的是如何使用繼承提供更有用的程序接口。 而對(duì)象模式描述的是如何通過使用對(duì)象組合或?qū)?duì)象包含在其他對(duì)象里, 將對(duì)象組合成更大的一個(gè)結(jié)構(gòu)。
1、單例模式可以保證:在一個(gè)應(yīng)用程序中,一個(gè)類有且只有一個(gè)實(shí)例, 并提供一個(gè)訪問它的全局訪問點(diǎn)。 2、在程序設(shè)計(jì)過程中,有很多情況需要確保一個(gè)類只有一個(gè)實(shí)例。 例如: windows系統(tǒng)中只能有一個(gè)窗口管理器 某個(gè)程序中只能有一個(gè)日志輸出系統(tǒng) 一個(gè)GUI類庫中,有且只有一個(gè)ImageManager 還有其他無數(shù)種情況


1、優(yōu)點(diǎn):該實(shí)現(xiàn)是一個(gè)"懶漢"單例模式,意味著只有在第一次調(diào)用GetInstance(),靜 態(tài)方法的時(shí)候才進(jìn)行內(nèi)存分配。如果整個(gè)程序不調(diào)用該靜態(tài)方法,則不會(huì)分配內(nèi)存。相對(duì)應(yīng)的是"餓漢"單例模式。 2、缺點(diǎn): 1) "懶漢"模式雖然有優(yōu)點(diǎn),但是每次調(diào)用GetInstance()靜態(tài)方法時(shí), 必須判斷NULL == m_instance,使程序相對(duì)開銷增大。 2) 由于使用指針動(dòng)態(tài)內(nèi)存分配,我們必須在程序結(jié)束時(shí), 手動(dòng)的調(diào)用ReleaseInstance()靜態(tài)方法,進(jìn)行內(nèi)存的釋放。 3) 教科書標(biāo)準(zhǔn)實(shí)現(xiàn)大的缺點(diǎn)是線程不安全。 根據(jù)該模式的定義,整個(gè)應(yīng)用程序中,不管是單線程,還是多線程, 都只能有且只有該類的一個(gè)實(shí)例。而在多線程中會(huì)導(dǎo)致多個(gè)實(shí)例的產(chǎn)生, 從而導(dǎo)致運(yùn)行代碼不正確以及內(nèi)存的泄露。



1、我們創(chuàng)建3個(gè)輔助線程,外加main主線程,一共有4個(gè)線程。 2、我們在每個(gè)輔助線程里面調(diào)用GetInstance()靜態(tài)方法,由于每個(gè)線程 回調(diào)函數(shù)速度非???,導(dǎo)致每個(gè)線程在判斷NULL==m_instance時(shí), 都返回true,從而導(dǎo)致每個(gè)線程回調(diào)函數(shù)都會(huì)創(chuàng)建一個(gè)CSingleton1對(duì) 象并返回指向該對(duì)象的指針。 3、我們根本沒辦法進(jìn)行CSingleton1的內(nèi)存釋放,因?yàn)樵诙嗑€程中, 我們根本不知道是創(chuàng)建了1個(gè)、2個(gè)或3個(gè)CSingleton1的實(shí)例

Meyers Singleton Pattern的優(yōu)缺點(diǎn) :
1、優(yōu)點(diǎn): 1) 該實(shí)現(xiàn)是一個(gè)"懶漢"單例模式,意味著只有在第一次調(diào)用GetInstance()時(shí)才會(huì) 實(shí)例化。 2) 不需要每次調(diào)用GetInstance()靜態(tài)方法時(shí),必須判斷NULL==m_instance,效 率相對(duì)高一些。 3) 使用對(duì)象而不是指針分配內(nèi)存,因此自動(dòng)回調(diào)用析構(gòu)函數(shù),不會(huì)導(dǎo)致內(nèi)存泄露。 4) 在多線程下的確能夠保證有且只有一個(gè)實(shí)例產(chǎn)生。 2、缺點(diǎn): 在某些編譯器中,在多線程情況下,并不是真正意義上的線程安全的實(shí)現(xiàn)
我們修改一下前面線程函數(shù)


這是因?yàn)镃++中構(gòu)造函數(shù)并不是線程安全的。 C++中的構(gòu)造函數(shù)簡單來說分兩步: 第一步:內(nèi)存分配 第二步:初始化成員變量 由于多線程的關(guān)系,可能當(dāng)我們在分配內(nèi)存好了以后,還沒來得急初始化成員變量,就 進(jìn)行線程切換,另外一個(gè)線程拿到所有權(quán)后,由于內(nèi)存已經(jīng)分配了,但是變量初始化還 沒進(jìn)行,因此打印成員變量的相關(guān)值會(huì)發(fā)生不一致現(xiàn)象。 結(jié)論:Meyers方式雖然能確保在多線程中產(chǎn)生唯一的實(shí)例,但是不能確保成員變量的值是否正確.
1) 是一個(gè)"懶漢"單例模式,按需內(nèi)存分配。 2) 基于模板實(shí)現(xiàn),具有很強(qiáng)的通用性。 3) 自動(dòng)內(nèi)存析構(gòu),不存在內(nèi)存泄露問題(使用std::tr1::shared_ptr)。 4) 在多線程情況下,是線程安全的。 5) 盡可能的高效。(線程安全必定涉及到線程同步,線程同步分為內(nèi)核級(jí)別和用戶級(jí)別的 同步對(duì)象,用戶級(jí)別效率遠(yuǎn)高于內(nèi)核級(jí)別的同步對(duì)象,而用戶級(jí)別效率最高的是 InterlockedXXXX系列API)。 6) 這個(gè)實(shí)際上也是一個(gè)Double-Checked Locking實(shí)現(xiàn)的單例模式。是傳統(tǒng)的Double- Checked-Locking變異版本。



線程安全的單例模式(基礎(chǔ)版)的測試


線程安全的單例模式(基礎(chǔ)版)存在的不足


單例的一個(gè)原則就是禁止構(gòu)造函數(shù)和析構(gòu)函數(shù)為public,防止外部實(shí)例化,僅允許調(diào)用GetInstance()等靜態(tài)方法進(jìn)行初始化。
由于使用模板技術(shù),如果我們不將基類和子類的構(gòu)造和析構(gòu)函數(shù)設(shè)置為public級(jí)別,模板實(shí)例化導(dǎo)致編譯器報(bào)錯(cuò)。
1、修正構(gòu)造函數(shù):
1) 將基類CSingletonPtr的構(gòu)造函數(shù)為protected訪問級(jí)別。

2) 每一個(gè)繼承自CSingletonPtr的子類也將構(gòu)造函數(shù)聲明為protected訪問級(jí)別,并在繼 承類中聲明友元類。

3) 在上述代碼設(shè)定以后,我們會(huì)發(fā)現(xiàn)對(duì)于構(gòu)造函數(shù),通過子類授權(quán)給基類的方式,我們 能夠很順利的通過編譯,代碼正確的運(yùn)行。
這樣我們解決了防止第三方調(diào)用Manager的構(gòu)造函數(shù),Manager類的構(gòu)造函數(shù)只允許在
GetInstance()靜態(tài)方法中被調(diào)用。
2、修正析構(gòu)函數(shù):
我們會(huì)發(fā)現(xiàn),對(duì)于析構(gòu)函數(shù),它依舊是public訪問級(jí)別的,為什么不讓析構(gòu)函數(shù)也聲明為
protected級(jí)別呢?
因?yàn)橛捎谖覀兪褂昧藄td::tr1::shared_ptr(與boost::shared_ptr基本一致),Manager類的析構(gòu)委托給了shared_ptr ,而Manager授權(quán)給的是其基類。所以如果我們將析構(gòu)函數(shù)設(shè)置為protected級(jí)別,編譯器會(huì)報(bào)錯(cuò)。
那么我們第一個(gè)反應(yīng)就是我們繼續(xù)在Manager中授權(quán)shared_ptr。但是沒成功??赡苁怯捎趕hared_ptr實(shí)現(xiàn)的機(jī)制導(dǎo)致不能成功。
難道我們真的沒有辦法將析構(gòu)函數(shù)修正為受保護(hù)級(jí)別嗎?
1) 在基類CSingletonPtr中聲明并實(shí)現(xiàn)一個(gè)訪問級(jí)別為private的嵌套類Deleter,代表一個(gè)刪除器。 重載函數(shù)調(diào)用操作符,該刪除器類實(shí)際是一個(gè)仿函數(shù)對(duì)象。


2) 在GetInstance()靜態(tài)函數(shù)中增加刪除器設(shè)置代碼,見圖紅色部分。 一旦我們設(shè)置好刪除器,那么在shared_ptr析構(gòu)時(shí)不會(huì)直接調(diào)用delete而是調(diào)用刪除器。 這樣我們 就將子類T的析構(gòu)函數(shù)隱藏起來,不被外部調(diào)用。 3) 每一個(gè)繼承自CSingletonPtr的子類也將構(gòu)造函數(shù)聲明為protected訪問級(jí)別,但不需 要聲明授權(quán)友元類。
通過上述步驟,我們將基類和子類的構(gòu)造函數(shù)都聲明為受保護(hù)級(jí)別,以防止外部調(diào)用。這樣整個(gè)單例子類的生命周期都由shared_ptr控制。
3、修正GetInstance()靜態(tài)方法:
基礎(chǔ)版的GetInstance()靜態(tài)方法返回的是tr1::shared_ptr結(jié)構(gòu),這樣導(dǎo)致每次調(diào)用
GetInstance()都會(huì)使shared_ptr的引用計(jì)數(shù)加1并調(diào)用shared_ptr的拷貝構(gòu)造函數(shù)。在
調(diào)用完成GetInstance()->Print方法后,又將臨時(shí)產(chǎn)生的shared_ptr對(duì)象引用計(jì)數(shù)減1,
這樣對(duì)效率有非常大的影響。
我們要避免這種情況,那么我們要做的是修改代碼,直接在GetInstance()中返回T的引用。

更進(jìn)一步,我們使用編譯器提供的本質(zhì)函數(shù)。


1、優(yōu)點(diǎn):該實(shí)現(xiàn)是一個(gè)"懶漢"單例模式,意味著只有在第一次調(diào)用GetInstance() 靜態(tài)方法的時(shí)候才進(jìn)行內(nèi)存分配。 通過模板和繼承方式,獲得了足夠通用的能力。 在創(chuàng)建單例實(shí)例的時(shí)候,具有線程安全性。 通過智能指針方式,防止內(nèi)存泄露。 具有相對(duì)的高效性。 2、 缺點(diǎn):肯定沒有單線程版本的效率高。 每個(gè)子類必須要授權(quán)基類,我們可以寫一個(gè)宏減少輸入: #define DECLARE_SINGLETON_CLASS(type) \ friend class CSingletonPtr;
餓漢模式意味著在主線程(main函數(shù)代表主線程)之前就對(duì)類進(jìn)行內(nèi)存分配和初始化。實(shí)現(xiàn)代碼如下:





從編譯器以及是否線程安全方面考慮:
1、如果你使用vc6編譯器,請放棄設(shè)計(jì)模式。 2、如果你整個(gè)程序是單線程的,那么標(biāo)準(zhǔn)模式或Meyers單例模式是你最佳選擇。 3、如果你使用符合C++0X標(biāo)準(zhǔn)的編譯器的話,由于C++0X標(biāo)準(zhǔn)規(guī)定:要求編譯器保證內(nèi) 部靜態(tài)變量的線程安全性。 目前只有VS2015支持內(nèi)部靜態(tài)變量的線程安全性,因此Meyers單例模式是你最佳選擇。 4、如果你使用VC6以后,vc2010以下版本的編譯器的話,并且需要線程安全,則使用實(shí)現(xiàn)的Double-Checked-Locking版本的單件模式。
從單例模式實(shí)現(xiàn)的角度考慮:
1、總是避免第三方調(diào)用拷貝構(gòu)造函數(shù)以及賦值操作符 2、總是避免第三方調(diào)用構(gòu)造函數(shù) 3、盡量避免第三方調(diào)用析構(gòu)函數(shù) 4、總是需要一個(gè)靜態(tài)方法用于全局訪問
本篇文檔寫于2010年,當(dāng)時(shí)還是以vs2008為主,因此并沒有符合c++0x標(biāo)準(zhǔn)?,F(xiàn)如今都vs2015了,c++11標(biāo)準(zhǔn)都普及了,因此Meyers單例模式是你最佳選擇。
不過關(guān)于上面的shared_ptr方面的應(yīng)用,還是很有價(jià)值的。shared_ptr已成為目前c++內(nèi)存操作的主流技術(shù)。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。