【设计模式】单例模式


1. 什么是单例模式

  该模式的主要目的就是确保某个类有且仅有一个实例存在,在任何位置都可以通过接口获取这个唯一实例。如:

  • 设备管理器:系统可以有多个设备,但是只有一个设备管理器,用于管理设备驱动;
  • 数据池:用来缓存数据的数据结构,需要在一处写,多处读取,或者多处写,多处读取;

2. 单例模式的应用

  • 特征

  1. 确保全局只有一个实例(static private)被创建:

    为此,单例类只能提供私有(private)构造函数,保证用户不能随意自定义实例;实例靠自己来创建;

  2. 提供该实例的全局访问接口:

    通过静态(static)公有方法,创建或者获取该静态私有对象;

  3. 禁止赋值和拷贝

  4. 线程安全

  • 如何使用

  越小越好,越简单越好,线程安全,内存不泄露。

3. 单例模式的几种实现及优缺点

  • 懒汉式:懒汉很懒,只有用的时候才实例化

  写法1:(基础写法,有缺陷)

class Singleton
{
private:
    Singleton() {}
    Singleton(Singleton&) = delete; //C++11特性,意味着不可调用,负责报错
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* m_pInstance;

public:
    ~Singleton() {}
    static Singleton* getInstance()
    {
        if(!m_pInstance)
          m_instance_ptr = new Singleton;

        return m_pInstance;
    }
};

Singleton* Singleton::m_pInstance = NULL;       

  这是个最基础的单例模式实现,他有那些问题呢?

  • 线程安全问题,如多个线程同时尝试获取单例,判断m_pInstance都为空,实例化多个对象。解决方法:加锁
  • 内存是否泄漏?只负责new出对象,却不负责delete对象,导致内存泄漏?一般而言,单例类实例都是常驻的,不需要手动释放。如果非要释放,不能在析构函数中,可以考虑增加一个destroy()方法,或者智能指针;

  写法2:(安全写法,也有不足)

class Singleton
{
public:
typedef std::shared_ptr sharePtr;
~Singleton() {} Singleton(Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; static Ptr getInstance() { //double checked lock if(!m_pInstance) { std::lock_guard lk(m_mutex); if(!m_pInstance)   m_pInstance = std::shared_ptr(new Singleton); } return m_instance_ptr; } private: Singleton() {} static sharePtr m_pInstance; static std::mutex m_mutex; }; Singleton::sharePtr Singleton::m_pInstance = NULL; std::mutex Singleton::m_mutex;

  不足之处在于:

  • 约束用户也要使用只能指针,非必要不约束;
  • 锁也有开销,大数据量情况下,会成为严重的性能瓶颈
  • 代码增多;
  • 某些平台(编译器和指令架构不同),双检锁会失效;

  写法3:(最推荐写法,局部静态变量 - magic static)

class Singleton
{
public:
    ~Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    static Singleton& getInstance()
    {
        static Singleton instance;
        return instance;

    }
private:
    Singleton() {}
};

  又叫做Meyers' SingletonMeyer's的单例,是著名《Effective C++》作者Meyers提出的,用到的特性是C++11中的Magic Static特性: 

If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。

  这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性:

  • 通过局部静态变量特性,保证了线程安全(C++11,GCC>4.3,VS2015)
  • 不需要共享指针,代码简洁;
  • 注意使用时,需要声明单例的引用才能获取对象;否则返回指针的话,无法避免用户delete导致对象提前销毁。

  写法4:单例模板

  待续。。。