需求描述(實(shí)現(xiàn)一個(gè)線程安全且無內(nèi)存泄漏的C++單例模式):
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變異版本励背。
線程安全的單例模式實(shí)現(xiàn)(基礎(chǔ)版)
線程安全的單例模式(基礎(chǔ)版)的測(cè)試
線程安全的單例模式(基礎(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ò)衅疙。
線程安全的單例模式(基礎(chǔ)版)的修正
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。但是沒成功帝簇∨枪可能是由于shared_ptr實(shí)現(xiàn)的機(jī)制導(dǎo)致不能成功。
難道我們真的沒有辦法將析構(gòu)函數(shù)修正為受保護(hù)級(jí)別嗎?
山窮水盡疑無路丧肴,柳暗花明又一村残揉!
強(qiáng)大的shared_ptr刪除器的出現(xiàn)解決了我們的問題!
1) 在基類CSingletonPtr中聲明并實(shí)現(xiàn)一個(gè)訪問級(jí)別為private的嵌套類Deleter芋浮,代表一個(gè)刪除器抱环。
重載函數(shù)調(diào)用操作符,該刪除器類實(shí)際是一個(gè)仿函數(shù)對(duì)象途样。
2) 在GetInstance()靜態(tài)函數(shù)中增加刪除器設(shè)置代碼江醇,見圖紅色部分。
一旦我們?cè)O(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<T>結(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ù)捺球。
線程安全的單例模式(修正版)的優(yōu)缺點(diǎn)
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 <type>;
餓漢類型單例模式實(shí)現(xiàn)(終極版)
餓漢模式意味著在主線程(main函數(shù)代表主線程)之前就對(duì)類進(jìn)行內(nèi)存分配和初始化螺捐。實(shí)現(xiàn)代碼如下:
餓漢類型單例模式測(cè)試
單例模式四種實(shí)現(xiàn)總結(jié)
從編譯器以及是否線程安全方面考慮:
1、如果你使用vc6編譯器矮燎,請(qǐng)放棄設(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)變量的線程安全性茫虽。(vc2010及以上版本。因此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)。現(xiàn)如今都vs2015了蜈块,c++11標(biāo)準(zhǔn)都普及了鉴腻,因此Meyers單例模式是你最佳選擇。
不過關(guān)于上面的shared_ptr方面的應(yīng)用百揭,還是很有價(jià)值的爽哎。shared_ptr已成為目前c++內(nèi)存操作的主流技術(shù)。
**