一、什么是单例模式
一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
二、代码结构实现单例模式
单例模式设计思路:在类中声明一个指向自身的静态变量,将构造函数设为私有,给外部提供一个全局函数,唯一能获取这个类的指针的方法。即:外部只能通过一个函数获取相同的一个指针,就是单例模式的核心。
2.1 单例模式简单版(不支持同步)
a.类的声明
class SingleInstance
{
public:
static SingleInstance*GetInstance();
protected:
SingleInstance()=default;
static SingleInstance*mInstance;
};
b.初始化静态变量
SingleInstance* SingleInstance::mInstance = nullptr;
c.实现GetInstance()方法
SingleInstance* SingleInstance::GetInstance()
{
if (mInstance == nullptr)
mInstance = new SingleInstance;
return mInstance;
}
简单版的单例模式,在多线程编程时候不能正常工作,会产生几个或多个实例,是因为线程进入mInstance == nullptr条件的时候,多个线程同时在这个位置,进而new 了多个实例,这个问题在后面的增强版进行优化。
2.1 单例模式增强版(支持同步)
a.类的声明(和简单版一致)
b.初始化静态变量(和简单版一致)
c.添加互斥量对象(用于线程同步,线程同步的方式有很多种,根据自己情况挑选,以后再专门分析各种同步手法)
SingleInstance* SingleInstance::mInstance = nullptr;
std::mutex g_mutex;
d.实现GetInstance()方法,加入同步代码
SingleInstance* SingleInstance::GetInstance()
{
if (mInstance == nullptr)
{
g_mutex.lock();
if(mInstance==nullptr)
mInstance = new SingleInstance;
g_mutex.unlock();
}
return mInstance;
}
可以看出,多线程编程时,单例模式依然能正常工作,只有当前获得锁的线程能实例化,并且同时不能让其他线程实例化,系统中始终只有一套实例,这就是单例模式的灵魂。
三、代码实现的注意事项
在编写单例模式时候,由于印象不够深刻,往往会忽略一下细节:
1、默认构造函数,或者自定义构造函数没有私有化,即外部可以直接new,单例模式失效
2、静态变量指针mInstance没有初始化,默认成其他地址,导致始终没有new成功,地址访问报错
3、简单版忘记添加同步锁,导致单例失效
四、单例模式的使用方法
在实际开发过程中,如何切合实际情况使用单例模式,是程序员开发经验和能力的体现。通常一个完善的系统或框架,都会有一份比较完整的数据,这种数据需要确保只有一份,并且公开,每个用户拿到都是相同的一份,一旦修改,所有用户拿到的也为最新的。如多个窗口打开垃圾桶,多个窗口都拿到单一的句柄,如封装好的线程池对象,整个窗口的事例,等等,我们并不少见getInstance这样的一款函数,底层都是单例模式的实现。
假设一个场景,有一款游戏,游戏中有用户的很多信息,如昵称、性别等,在整个游戏过程中,这些信息可能在不同的窗口被获取,或者更改一些数据,这样的数据在系统只保留一份即可,使用单例模式来演示:
class CXGame
{
public:
static CXGame*GetInstance();
//各种修改信息、获取信息的成员函数
...
protected:
CXGame() = default;
static CXGame*mInstance;
string nickname;
string sex;
...
};
CXGame* CXGame::GetInstance()
{
if (mInstance == nullptr)
{
g_mutex.lock();
if (mInstance == nullptr)
mInstance = new CXGame;
g_mutex.unlock();
}
return mInstance;
}
CXGame* CXGame::mInstance = nullptr;
std::mutex g_mutex;
每当一个新的模块加入新的功能,都可以通过GetInstance()来获取实例,进而修改或获取相关信息。
五、优缺点
1、优点:只有一个实例,节省系统资源,避免频繁创建、销毁;优化和共享资源访问。
2、缺点:不适应频繁变化的对象;没有接口,扩展困难。