从零开始最简单Qt/C++设计模式总结七:单例模式(懒汉单例和饿汉单例)
单例模式就是指一个类在整个程序中只有一个实例。确保一些不需要重复创建的类创建多余的实例。特别是某些工具类,在所有地方使用该类都只需要一个实例。
基本思路就是在单例类内部创建一个静态的自身对象,并自己管理自己。
下面代码用锤子Hammer来表示这个工具,简单的实现如下
1、懒汉模式
懒汉模式就是指铁匠是个懒汉,这个锤子类的实例在没人用的时候,铁匠不去造它,这样做能节约铁匠铺的空间。
#include <QCoreApplication>
#include <QDebug>
///NOTE7单例模式--懒汉单例的线程不安全做法
class Hammer
{
private:
static Hammer* m_pHammer;
Hammer()
{
qDebug() << "make a new hammer!";
}
~Hammer() {}
Hammer(const Hammer&) = delete;//单例类建议屏蔽拷贝构造函数
Hammer &operator =(const Hammer&rhs) = delete;//单例类建议屏蔽拷贝运算符
public:
static Hammer* GetHammer()
{
if(m_pHammer == nullptr)
{
qDebug() << "make a new hammer!";
//由于静态变量的资源在程序最后全部统一释放,所以这个new没有对应的delete问题不大,严谨的话也可以加上释放过程
m_pHammer = new Hammer();
return m_pHammer;
}
}
void Beat()
{
qDebug() << "I can beat";
}
};
Hammer* Hammer::m_pHammer = nullptr;
int main(int, char **)
{
Hammer::GetHammer()->Beat();//获取锤子进行敲打
getchar();
return 0;
}
上述单例是存在线程安全问题的,比如有两个客人(多线程)同时需要使用锤子,两个客人的访问流程同时走到了锤子是否被实例化那一个if语句中,此时条件都成立,因此就会产生两把不一样的锤子供客人使用,这明显是不符合设计初衷的。可以通过线程操作的一些方法解决,下面再说。
2、饿汉模式
这个模式可以较简单地解决同时有客人光顾的问题。即铁匠是个饿汉,总是担心生意上门了却没锤子可提供,因此铁匠铺开张的时候(类定义时)就把锤子造好等人来用。代码如下:
//饿汉单例
class Hammer
{
private:
static Hammer* m_pHammer;
Hammer()
{
qDebug() << "make a new hammer!";
}
~Hammer() {}
Hammer(const Hammer&) = delete;//单例类建议屏蔽拷贝构造函数
Hammer &operator =(const Hammer&rhs) = delete;//单例类建议屏蔽拷贝运算符
public:
static Hammer* GetHammer()
{
return m_pHammer;//客人访问时直接提供生产好的锤子
}
void Beat()
{
qDebug() << "I can beat";
}
};
//先将锤子生产好
Hammer* Hammer::m_pHammer = new Hammer();
int main(int, char **)
{
Hammer::GetHammer()->Beat();//获取锤子进行敲打
getchar();
return 0;
}
可以看出,和懒汉模式唯一的区别就在于是否先生产好锤子。这种方式会使得空间占用变多,如果没人使用就白白浪费了。
3懒汉模式的改进
懒汉模式可以采用线程锁的方式进行改进,在铁匠铺装一个自动锁,每次客人进门时自动把门锁上,等锤子造好了再开锁。这样限制两个客人同时对懒汉铁匠进行访问。
#include <QCoreApplication>
#include <QDebug>
#include<thread>
#include<mutex>
#include <Windows.h>
using namespace std;
//懒汉单例的线程安全做法
class Hammer
{
private:
static Hammer* m_pHammer;
static mutex m_Mutex;//加一把锁
Hammer()
{
qDebug() << "make a new hammer!";
}
~Hammer() {}
Hammer(const Hammer&) = delete;//单例类建议屏蔽拷贝构造函数
Hammer &operator =(const Hammer&rhs) = delete;//单例类建议屏蔽拷贝运算符
public:
static Hammer* GetHammer()
{
if(m_pHammer == nullptr)//双重判断,如果没锤子,估计会发生客人同时来店的情况,先锁上,然后再看有没有锤子
{
m_Mutex.lock();
if(m_pHammer == nullptr)
{
//由于静态变量的资源在程序最后全部统一释放,所以这个new没有对应的delete问题不大,严谨的话也可以加上释放过程
m_pHammer = new Hammer();
}
m_Mutex.unlock();
}
return m_pHammer;
}
void Beat()
{
qDebug() << "I can beat";
}
};
Hammer*Hammer::m_pHammer = nullptr;
mutex Hammer::m_Mutex;
void Guest1()//客人1需要锤子
{
Sleep(1);
Hammer::GetHammer()->Beat();
}
void Guest2()//客人2需要锤子
{
Sleep(1);
Hammer::GetHammer()->Beat();
}
int main(int, char **)
{
thread thread1(Guest1), thread2(Guest2);
thread1.detach();
thread2.detach();
getchar();
return 0;
}
4更优雅的方案
以上几种方式都有一个初始化顺序问题,如果有两个单例,其中一个包含另一个,当被包含的未被初始化时会出问题。《EffectiveC++》item4提到了这一点,并提出一种优雅的解决方案。原文是:“将每个non-local static 对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不是直接指涉这些对象。”做法如下:
#include <QCoreApplication>
#include <QDebug>
#include<thread>
#include<mutex>
#include <Windows.h>
using namespace std;
class Hammer
{
public:
static Hammer& GetHammer();
void Beat()
{
qDebug() << "I can beat";
}
private:
Hammer()
{
qDebug() << "make a new hammer!";
}
~Hammer()
{
}
Hammer(const Hammer&) = delete; //单例类建议屏蔽拷贝构造函数
Hammer& operator=(const Hammer&) = delete; //单例类建议屏蔽拷贝运算符
};
Hammer& Hammer::GetHammer()
{
static Hammer hammer;
return hammer;
}
void Guest1()//客人1需要锤子
{
Sleep(1);
Hammer::GetHammer().Beat();
}
void Guest2()//客人2需要锤子
{
Sleep(1);
Hammer::GetHammer().Beat();
}
int main(int, char **)
{
thread thread1(Guest1), thread2(Guest2);
thread1.detach();
thread2.detach();
getchar();
return 0;
}
可以看出,该方法既简单又线程安全,推荐单例模式采用这种方法。
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- YII框架常用技巧总结
- PHP连接SQL Server的方法分析【基于thinkPHP5.1框架】
- PHP切割汉字的常用方法实例总结
- Laravel Validator 实现两个或多个字段联合索引唯一
- php实现的顺序线性表示例
- pytorch快速搭建神经网络_Sequential操作
- PHP7使用ODBC连接SQL Server2008 R2数据库示例【基于thinkPHP5.1框架】
- Yii框架引入coreseek分页功能示例
- 使用keras内置的模型进行图片预测实例
- PHP convert_cyr_string()函数讲解
- 在keras中model.fit_generator()和model.fit()的区别说明
- 浅谈matplotlib 绘制梯度下降求解过程
- Ajax+PHP实现的分类列表框功能示例
- keras实现图像预处理并生成一个generator的案例
- Django+RestFramework API接口及接口文档并返回json数据操作