设计模式之单例模式


title: 设计模式之单例模式
longzy:2019-2-23


单列模式,顾名思义,一个类只有一个实例。所以单列模式的特征为:

  • 只有一个实例
  • 必须提供一个全局访问点(静态成员方法或静态静态成员变量)
  • 不可以复制拷贝
  • 如果得到是静态成员对象指针的地址,必须提供一个释放指针的方法

根据以上描述的特征,那么一个简单的单例模式就诞生了,如下代码所示

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;

我们分析下上面代码,如果在单线程环境下面运行,没有什么问题,假设在多线程环境中,两个线程同时运行到m_pInstance == nullptr,此时条件为真,那么就会创建两个实例,这不符合单列的特征,那么在这里需要改进,在改进前,我们先用c++11实现一个不可复制的类Noncopyable,以后让其继承该类即可

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

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

};

然后改进后的单列类如下

#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;

我们分析下上面代码,解决线程安全问题无非就是加锁,但是如果线程很多情况下,每个线程都要等拿到锁的线程运行结束后才继续执行,这样无疑会导致大量的线程阻塞,那么该如何解决呢,解决办法就是在锁之前先判断是否为nullptr,于是改进后的代码如下:

#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;

继续分析上面的代码,其实这就是所谓的双检锁机制。但是请注意,如果数据量很大的情况,加锁释放锁本来就是耗时的操作,所以在大数据情况下,这种双检锁机制的单列模式性能就显得堪忧了,所以我们应该避免加锁操作,于是就出现了另外一种单列模式

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();

继续分析上面代码,巧妙的使用了静态成员初始化的特性,静态成员初始化是在程序进入主函数之前,主线程以单线程的方式完成了初始化操作,所以很好的解决了线程安全问题。但是没有提供一个销毁静态实例的方法。于是我们可以考虑返回静态成员对象的地址,然后利用对象的自动销毁功能来做释放操作,于改进后的代码如下:

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;

这样的实现应该算是比较好了,但是还有比这更好的完美方法,利用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的可变模板参数,放一个万能的单列模式

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;

}

由于本人水平有限,若有错误,欢迎指出,谢谢!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容