单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。[DP]
因为需要只有一个实例对象,最好的控制方式就是类自身来控制,而不是在其他类或者全局变量来控制实例对象的创建。
我对于定义的理解是,为了保证一个类仅有一个实例,所以必须一个访问它的全局访问点。保证一个类仅有一个实例,换言之就是类的构造不对外开放,由其他方式来提供给外部使用,即提供一个访问的全局访问点或者接口。
这样我们就可以大概获得Singleton类的大概结构:
#pragma once class Singleton { private: //①构造函数私有化 Singleton(){}; public: //②提供一个全局访问点,由它来提供给外部Singleton的指针 //因为要实现在没有Singleton对象的情况下获取到Singleton的指针,所以使用static //这样就可以通过类名来获取到对象指针了 static Singleton* GetInstance(); private: //③ 用来返回的唯一的实例指针 static Singleton* m_instance; };
#include "Singleton.h" //类内的全局变量需要独立初始化 Singleton* Singleton::m_instance = nullptr; //懒汉式 即 使用再加载 Singleton* Singleton::GetInstance() { if (!m_instance) //如果m_instance等于nullptr,进入 { m_instance = new Singleton(); //构造函数私有的目的就在于此 } return m_instance; }
以上的单例模式在单线程是安全的,但是在多线程的时候会出现问题。假设有两个线程同时访问GetInstance函数,且同时走到if (!m_instance) 的时候,会都得到m_instance为nullptr的信息,这样就会各自都会创建对象,这样对象就不在唯一了,与单例模式的定义不符合。主要原因是在创建完成前,大家都可以去创建。
既然会同时访问,那就加锁,
#pragma once #include <mutex> class Singleton { private: //①构造函数私有化 Singleton() {}; public: //②提供一个全局访问点,由它来提供给外部Singleton的指针 //因为要实现在没有Singleton对象的情况下获取到Singleton的指针,所以使用static //这样就可以通过类名来获取到对象指针了 static Singleton* GetInstance(); private: //③ 用来返回的唯一的实例指针 static Singleton* m_instance; static std::mutex m_mutex; };
#include "Singleton.h" //类内的全局变量需要独立初始化 Singleton* Singleton::m_instance = nullptr; std::mutex Singleton::m_mutex; //懒汉式 即 使用再加载 Singleton* Singleton::GetInstance() { m_mutex.lock(); if (!m_instance) //如果m_instance等于nullptr,进入 { m_instance = new Singleton(); //构造函数私有的目的就在于此 } m_mutex.unlock(); return m_instance; }
这样就保证了单例模式的安全,但是现在的代码,每次GetInstance都需要加锁,这样带来的开销很大,会降低效率。所以就有了deouble-check机制的产生。再回顾我们刚刚说的,主要原因是在创建完成前,大家都可以去创建。但如果已经创建好,再有多线程访问,那就不会有这些问题。
所以我们要对后续访问去锁,对创建加锁:
#include "Singleton.h" //类内的全局变量需要独立初始化 Singleton* Singleton::m_instance = nullptr; std::mutex Singleton::m_mutex; //懒汉式 即 使用再加载 Singleton* Singleton::GetInstance() { if (!m_instance) { m_mutex.lock(); if (!m_instance) //如果m_instance等于nullptr,进入 { m_instance = new Singleton(); //构造函数私有的目的就在于此 } m_mutex.unlock(); } return m_instance; }
这就是double-check模式,这样创建完成后的后续只要走第一个if判断,而且不会进if内加锁灯操作,这样就维护了后续的效率。
如果想要主动销毁到Singleton对象,可以给类添加一个Release函数,来完成对单例对象的主动释放。谨慎,可能会带来野指针的问题,因为可能其他地方还持有着指针,你这时候释放了指针的空间,那么持有者后续进行访问的时候会出现问题。
至此,懒汉式单例就结束了。