設(shè)計模式之單例模式


title: 設(shè)計模式之單例模式
longzy:2019-2-23


單列模式颠印,顧名思義,一個類只有一個實(shí)例拥刻。所以單列模式的特征為:

  • 只有一個實(shí)例
  • 必須提供一個全局訪問點(diǎn)(靜態(tài)成員方法或靜態(tài)靜態(tài)成員變量)
  • 不可以復(fù)制拷貝
  • 如果得到是靜態(tài)成員對象指針的地址怜瞒,必須提供一個釋放指針的方法

根據(jù)以上描述的特征,那么一個簡單的單例模式就誕生了般哼,如下代碼所示

template <typename T>
class Singleton
{
public:
    static T* Instance()
    {
        if (m_pInstance == nullptr)
            m_pInstance = new T();
        return m_pInstance;
    }
    static void DestroyInstance()
    {
        if (m_pInstance != nullptr)
        {
            delete m_pInstance;
            m_pInstance = nullptr;
        }
    }
private:
    Singleton(){}
    ~Singlento(){}
    
    Singleton(const Singleton&);
    Singleto& operator = (const Singleton&);
    
private:
    static T* m_pInstance;
};

template <typename T>
T* Singleton<T>::m_pInstance = nullptr;

我們分析下上面代碼盼砍,如果在單線程環(huán)境下面運(yùn)行尘吗,沒有什么問題,假設(shè)在多線程環(huán)境中浇坐,兩個線程同時運(yùn)行到m_pInstance == nullptr,此時條件為真睬捶,那么就會創(chuàng)建兩個實(shí)例,這不符合單列的特征近刘,那么在這里需要改進(jìn)擒贸,在改進(jìn)前,我們先用c++11實(shí)現(xiàn)一個不可復(fù)制的類Noncopyable觉渴,以后讓其繼承該類即可

class Noncopyable
{
protected:
    Noncopyable() = default;
    ~Noncopyable() = default;

    Noncopyable(const Noncopyable&) = delete;
    Noncopyable& operator = (const Noncopyable&) = delete;

};

然后改進(jìn)后的單列類如下

#include <mutex>
template <typename T>
class Singleton : Noncopyable
{
public:
    static T* Instance()
    {
            std::lock_guard<std::mutex> lock(m_mutex);
            if (m_pInstance == nullptr)
            {
                m_pInstance = new T();
            }
        return m_pInstance;
    }
    static void DestroyInstance()
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        if (m_pInstance != nullptr)
        {
            delete m_pInstance;
            m_pInstance = nullptr;
        }
    }
private:
    static T* m_pInstance;
    static std::mutex m_mutex;
};
template <typename T>
T* Singleton<T>::m_pInstance = nullptr;

template <typename T>
std::mutex Singleton<T>::m_mutex;

我們分析下上面代碼介劫,解決線程安全問題無非就是加鎖,但是如果線程很多情況下案淋,每個線程都要等拿到鎖的線程運(yùn)行結(jié)束后才繼續(xù)執(zhí)行座韵,這樣無疑會導(dǎo)致大量的線程阻塞,那么該如何解決呢踢京,解決辦法就是在鎖之前先判斷是否為nullptr誉碴,于是改進(jìn)后的代碼如下:

#include <mutex>
template <typename T>
class Singleton : Noncopyable
{
public:
    static T* Instance()
    {
        if (m_pInstance == nullptr)
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            if (m_pInstance == nullptr)
            {
                m_pInstance = new T();
            }
        }
        return m_pInstance;
    }
    static void DestroyInstance()
    {
        if (m_pInstance != nullptr)
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            if (m_pInstance != nullptr)
            {
                delete m_pInstance;
                m_pInstance = nullptr;
            }
        }
    }
private:
    static T* m_pInstance;
    static std::mutex m_mutex;
};
template <typename T>
T* Singleton<T>::m_pInstance = nullptr;

template <typename T>
std::mutex Singleton<T>::m_mutex;

繼續(xù)分析上面的代碼,其實(shí)這就是所謂的雙檢鎖機(jī)制瓣距。但是請注意黔帕,如果數(shù)據(jù)量很大的情況,加鎖釋放鎖本來就是耗時的操作蹈丸,所以在大數(shù)據(jù)情況下成黄,這種雙檢鎖機(jī)制的單列模式性能就顯得堪憂了,所以我們應(yīng)該避免加鎖操作逻杖,于是就出現(xiàn)了另外一種單列模式

template <typename T>
class Singleton : Noncpyable
{
public:
    static T* Instance()
    {
        return m_pInstance;
    }
private:
    static T* m_pInstance;
};

template <typename T>
T* Singleton<T>::m_pInstance = new T();

繼續(xù)分析上面代碼奋岁,巧妙的使用了靜態(tài)成員初始化的特性,靜態(tài)成員初始化是在程序進(jìn)入主函數(shù)之前荸百,主線程以單線程的方式完成了初始化操作闻伶,所以很好的解決了線程安全問題。但是沒有提供一個銷毀靜態(tài)實(shí)例的方法管搪。于是我們可以考慮返回靜態(tài)成員對象的地址,然后利用對象的自動銷毀功能來做釋放操作铡买,于改進(jìn)后的代碼如下:

template <typename T>
class Singleton : Noncopyable
{
public:
    static T* Instance()
    {
        return &m_Instance;
    }
private:
    static T m_Instance;
};

template <typename T>
T Singleton<T>::m_Instance;

這樣的實(shí)現(xiàn)應(yīng)該算是比較好了更鲁,但是還有比這更好的完美方法,利用linux的pthread_once和c++11的std::call_once

template <typename T>
class Singleton : Noncopyable
{
public:
    static T* Instance()
    {
        std::call_once(m_flag,&Singleton::Init);   //c++11
        //pthread_once(&m_ponce,&Singleton::Init);  //linux
        return m_pInstance;
    }

    static void DestroyInstance()
    {
        delete m_pInstance;
        m_pInstance = nullptr;
    }

private:
    static T* m_pInstance;
    static std::once_flag m_flag;
    //static pthread_once_t m_ponce;
    static void Init()
    {
        m_pInstance = new T();
    }
};

//template <typename T>
//pthread_once_t Singleton<T>::m_ponce = PTHREAD_ONCE_INIT;

template <typename T>
T* Singleton<T>::m_pInstance = nullptr;

另外放一個超級大招奇钞,利用c++11的可變模板參數(shù)澡为,放一個萬能的單列模式

template <typename T>
class Singleton : Noncopyable
{
public:
    template <typename... Args>
    static T* Instance(Args... args)
    {
        std::call_once(m_flag,&Singleton::Init,args);
        return m_pInstance;
    }
    static void DestroyInstance()
    {
        delete m_pInstance;
        m_pInstance = nullptr;
    }
private:
    static T* m_pInstance;
    static std::once_flag = m_flag;
    
    template <typename... Args>
    static void Init(Args&&.. args)
    {
        return new T(std::forward<Args>(args)...);
    }
};

template <typename T>
T* Singleton<T>::m_pInstance = nullptr;
template <typename T>
std::once_flag Singleton<T>::m_flag;

好了,單列模式就分析到這里了景埃,最后一種是萬金油媒至。
上一個測試代碼吧

#include <iostream>
#include "Singleton4.hpp"

struct A
{
    A()
    {
        std::cout << "A is constrcut!" << std::endl;
        num = 0;
    }
    ~A()
    {
        std::cout << "A is desconstruct!" << std::endl;
    }

    int num;
    void add(int n)
    {
        num = num + n;
    }
    int getNum()
    {
        return num;
    }
};

struct B
{
    int a_;
    explicit B(int a) : a_(a)
    {

    }
    int get()
    {
        return a_;
    }
};

int main()
{
    A *a = Singleton<A>::Instance();
    A *b = Singleton<A>::Instance();

    std::cout << "a is" << a << std::endl;
    std::cout << "b is" << b << std::endl;

    a->add(5);
    std::cout << "b.num=" << b->getNum() << std::endl;

    b->add(10);
    std::cout << "a.num=" << a->getNum() << std::endl;
    std::cout << "b.num=" << b->getNum() << std::endl;

    B *bb = Singleton<B>::Instance(5);

    std::cout << bb << std::endl;
    std::cout << "bb.a=" << bb->get() << std::endl;

}

由于本人水平有限顶别,若有錯誤,歡迎指出拒啰,謝謝驯绎!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谋旦,隨后出現(xiàn)的幾起案子剩失,更是在濱河造成了極大的恐慌,老刑警劉巖册着,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拴孤,死亡現(xiàn)場離奇詭異,居然都是意外死亡甲捏,警方通過查閱死者的電腦和手機(jī)演熟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來司顿,“玉大人芒粹,你說我怎么就攤上這事∶饣” “怎么了是辕?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長猎提。 經(jīng)常有香客問我获三,道長,這世上最難降的妖魔是什么锨苏? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任疙教,我火速辦了婚禮,結(jié)果婚禮上伞租,老公的妹妹穿的比我還像新娘贞谓。我一直安慰自己,他們只是感情好葵诈,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布裸弦。 她就那樣靜靜地躺著,像睡著了一般作喘。 火紅的嫁衣襯著肌膚如雪理疙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天泞坦,我揣著相機(jī)與錄音窖贤,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛赃梧,可吹牛的內(nèi)容都是我干的滤蝠。 我是一名探鬼主播,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼授嘀,長吁一口氣:“原來是場噩夢啊……” “哼物咳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起粤攒,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤所森,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后夯接,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體焕济,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年盔几,在試婚紗的時候發(fā)現(xiàn)自己被綠了晴弃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡逊拍,死狀恐怖上鞠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芯丧,我是刑警寧澤芍阎,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站缨恒,受9級特大地震影響谴咸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜骗露,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一岭佳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧萧锉,春花似錦珊随、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至禀崖,卻和暖如春衩辟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帆焕。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工惭婿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人叶雹。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓财饥,卻偏偏與公主長得像,于是被迫代替她去往敵國和親折晦。 傳聞我的和親對象是個殘疾皇子钥星,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評論 2 361

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