1.定义
单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。[DP]
2.理解
因为需要只有一个实例对象,最好的控制方式就是类自身来控制,而不是在其他类或者全局变量来控制实例对象的创建。
我对于定义的理解是,为了保证一个类仅有一个实例,所以必须一个访问它的全局访问点。保证一个类仅有一个实例,换言之就是类的构造不对外开放,由其他方式来提供给外部使用,即提供一个访问的全局访问点或者接口。
3.懒汉式(使用时再创建)
这样我们就可以大概获得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函数,来完成对单例对象的主动释放。谨慎,可能会带来野指针的问题,因为可能其他地方还持有着指针,你这时候释放了指针的空间,那么持有者后续进行访问的时候会出现问题。
至此,懒汉式单例就结束了。