C++設(shè)計(jì)模式之單例模式

話題二:指針*與引用&的區(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è)問題枫振,有兩種解決方式:

  1. 將 GetInstance() 函數(shù)的返回類型修改為指針喻圃,而非引用。
  2. 顯式地聲明類的拷貝構(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)行咙俩,所以,對我們來說湿故,尤為省心阿趁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末膜蛔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子脖阵,更是在濱河造成了極大的恐慌飞几,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件独撇,死亡現(xiàn)場離奇詭異,居然都是意外死亡躁锁,警方通過查閱死者的電腦和手機(jī)纷铣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來战转,“玉大人搜立,你說我怎么就攤上這事』毖恚” “怎么了啄踊?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長刁标。 經(jīng)常有香客問我颠通,道長,這世上最難降的妖魔是什么膀懈? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任顿锰,我火速辦了婚禮,結(jié)果婚禮上启搂,老公的妹妹穿的比我還像新娘硼控。我一直安慰自己,他們只是感情好胳赌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布牢撼。 她就那樣靜靜地躺著,像睡著了一般疑苫。 火紅的嫁衣襯著肌膚如雪熏版。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天缀匕,我揣著相機(jī)與錄音纳决,去河邊找鬼。 笑死乡小,一個(gè)胖子當(dāng)著我的面吹牛阔加,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播满钟,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼胜榔,長吁一口氣:“原來是場噩夢啊……” “哼胳喷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起夭织,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤吭露,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后尊惰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體讲竿,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年弄屡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了题禀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡膀捷,死狀恐怖迈嘹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情全庸,我是刑警寧澤秀仲,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站壶笼,受9級特大地震影響神僵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜覆劈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一挑豌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧墩崩,春花似錦氓英、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至铐拐,卻和暖如春徘键,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背遍蟋。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工吹害, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人虚青。 一個(gè)月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓它呀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子纵穿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354

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