设计模式之单例模式


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


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

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

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

template 
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 
T* Singleton::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 
template 
class Singleton : Noncopyable
{
public:
    static T* Instance()
    {
            std::lock_guard lock(m_mutex);
            if (m_pInstance == nullptr)
            {
                m_pInstance = new T();
            }
        return m_pInstance;
    }
    static void DestroyInstance()
    {
        std::lock_guard lock(m_mutex);
        if (m_pInstance != nullptr)
        {
            delete m_pInstance;
            m_pInstance = nullptr;
        }
    }
private:
    static T* m_pInstance;
    static std::mutex m_mutex;
};
template 
T* Singleton::m_pInstance = nullptr;

template 
std::mutex Singleton::m_mutex;

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

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

template 
std::mutex Singleton::m_mutex;

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

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

template 
T* Singleton::m_pInstance = new T();

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

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

template 
T Singleton::m_Instance;

这样的实现应该算是比较好了,但是还有比这更好的完美方法,利用linux的pthread_once和c++11的std::call_once

template 
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 
//pthread_once_t Singleton::m_ponce = PTHREAD_ONCE_INIT;

template 
T* Singleton::m_pInstance = nullptr;

另外放一个超级大招,利用c++11的可变模板参数,放一个万能的单列模式

template 
class Singleton : Noncopyable
{
public:
    template 
    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 
    static void Init(Args&&.. args)
    {
        return new T(std::forward(args)...);
    }
};

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

好了,单列模式就分析到这里了,最后一种是万金油。
上一个测试代码吧

#include 
#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::Instance();
    A *b = Singleton::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::Instance(5);

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

}

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

你可能感兴趣的:(设计模式之单例模式)