閑聊c/c++ 9: 設(shè)計(jì)模式: 單例模式真的簡(jiǎn)單嗎?(下)

需求描述(實(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ǔ)版)

Paste_Image.png
Paste_Image.png
Paste_Image.png

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

Paste_Image.png
Paste_Image.png

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

Paste_Image.png
Paste_Image.png

單例的一個(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í)別杖狼。
Paste_Image.png
2)  每一個(gè)繼承自CSingletonPtr的子類也將構(gòu)造函數(shù)聲明為protected訪問級(jí)別炼蛤,并在繼
      承類中聲明友元類。
Paste_Image.png
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ì)象途样。
Paste_Image.png
Paste_Image.png
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的引用。

Paste_Image.png

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

Paste_Image.png
Paste_Image.png

線程安全的單例模式(修正版)的優(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)代碼如下:

Paste_Image.png
Paste_Image.png

餓漢類型單例模式測(cè)試

Paste_Image.png
Paste_Image.png
Paste_Image.png

單例模式四種實(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ù)。
**

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末器一,一起剝皮案震驚了整個(gè)濱河市课锌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌祈秕,老刑警劉巖渺贤,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異请毛,居然都是意外死亡志鞍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門方仿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來固棚,“玉大人,你說我怎么就攤上這事仙蚜〈酥蓿” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵委粉,是天一觀的道長(zhǎng)呜师。 經(jīng)常有香客問我,道長(zhǎng)艳丛,這世上最難降的妖魔是什么匣掸? 我笑而不...
    開封第一講書人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮氮双,結(jié)果婚禮上碰酝,老公的妹妹穿的比我還像新娘。我一直安慰自己戴差,他們只是感情好送爸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般袭厂。 火紅的嫁衣襯著肌膚如雪墨吓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評(píng)論 1 300
  • 那天纹磺,我揣著相機(jī)與錄音帖烘,去河邊找鬼。 笑死橄杨,一個(gè)胖子當(dāng)著我的面吹牛秘症,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播式矫,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼乡摹,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了采转?” 一聲冷哼從身側(cè)響起聪廉,我...
    開封第一講書人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎故慈,沒想到半個(gè)月后板熊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡察绷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年邻邮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片克婶。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丹泉,靈堂內(nèi)的尸體忽然破棺而出情萤,到底是詐尸還是另有隱情,我是刑警寧澤摹恨,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布筋岛,位于F島的核電站,受9級(jí)特大地震影響晒哄,放射性物質(zhì)發(fā)生泄漏睁宰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一寝凌、第九天 我趴在偏房一處隱蔽的房頂上張望柒傻。 院中可真熱鬧,春花似錦较木、人聲如沸红符。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽预侯。三九已至致开,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間萎馅,已是汗流浹背双戳。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留糜芳,地道東北人飒货。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像耍目,于是被迫代替她去往敵國(guó)和親膏斤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容