C++设计模式--单例模式

参考C++设计模式 - 单例模式_单例模式大秦坑王-CSDN博客

单例模式简介

单例模式指的是,无论怎么获取,永远只能得到该类类型的唯一一个实例对象,那么设计一个单例就必须要满足下面三个条件:

  1. 构造函数私有化,这样用户就不能任意定义该类型的对象了
  2. 定义该类型唯一的对象
  3. 通过一个static静态成员方法返回唯一的对象实例

饿汉单例模式

饿汉式 单例模式,顾名思义,就是程序启动时就实例化了该对象,并没有推迟到第一次使用该对象时再进行实例化;如果运行过程中没有使用到,该实例对象就被浪费掉了。

复制代码
class CSingleton{
public:
	static CSingleton* getInstance()
	{
		return &single;
	}
private:
	static CSingleton single;
	CSingleton() { cout << "CSingleton()" << endl; }
	~CSingleton() { cout << "~CSingleton()" << endl; }
	CSingleton(const CSingleton&); 
    // 防止外部使用拷贝构造产生新的对象,如下面CSingleton s = *p1;
};
CSingleton CSingleton::single;

int main(){
	CSingleton *p1 = CSingleton::getInstance();
	CSingleton *p2 = CSingleton::getInstance();
	CSingleton *p3 = CSingleton::getInstance();
	cout<<p1<<" "<<p2<<" "<<p3<<endl;
	return 0;
}

是否线程安全?

饿汉单例模式中,单例对象定义成了一个static静态对象,它是在程序启动时,main函数运行之前就初始化好的,因此不存在线程安全问题,可以放心的在多线程环境中使用

懒汉单例模式

程序启动时,只对single指针初始化了空值,等第一次调用getInstance函数时,由于single指针为nullptr,才进行对象的实例化,所以是一个懒汉式单例模式(对象的实例化,延迟到第一次使用它的时候)。

复制代码
class CSingleton{
public:
	static CSingleton* getInstance(){
		if (nullptr == single){
			single = new CSingleton();
		}
		return single;
	}
private:
	static CSingleton *single;
	CSingleton() { cout << "CSingleton()" << endl; }
	~CSingleton() { cout << "~CSingleton()" << endl; }
	CSingleton(const CSingleton&);
};
CSingleton* CSingleton::single = nullptr;

int main(){
	CSingleton *p1 = CSingleton::getInstance();
	CSingleton *p2 = CSingleton::getInstance();
	CSingleton *p3 = CSingleton::getInstance();
	cout << p1 << " " << p2 << " " << p3 << endl;
	return 0;
}

上面new出来的对象,没见过delete,这样不好吧,当然了,有new没有delete,不配对啊!还有人说,管它呢,当前进程结束的时候,系统反正会回收分配给它的所有资源,包括未回收的内存,但是作为C++开发者,资源的分配和回收,我们必须要考虑清楚,不能糊涂,那么下面的修改感觉如何:

复制代码
int main(){
	CSingleton *p1 = CSingleton::getInstance();
	CSingleton *p2 = CSingleton::getInstance();
	CSingleton *p3 = CSingleton::getInstance();
	cout << p1 << " " << p2 << " " << p3 << endl;
	delete p1;    // 这里delete之前new过的对象,析构对象并且释放堆上的内存
	return 0;
}

这种方式怎么看,怎么不舒服,首先资源的释放如果交给用户来操作,难免会忘记写delete,又或者多次delete,成释放野指针了,所以上面释放单例对象资源的方式不够好,我们利用static静态对象在程序结束时自动析构这么一个特征,给出如下释放资源的代码,肯定比上面的方式要好,代码如下:

复制代码
class CSingleton{
public:
	static CSingleton* getInstance(){
		if (nullptr == single){
			single = new CSingleton();
		}
		return single;
	}
private:
	static CSingleton *single;
	CSingleton() { cout << "CSingleton()" << endl; }
	~CSingleton() { cout << "~CSingleton()" << endl; }
	CSingleton(const CSingleton&);

	// 定义一个嵌套类,在该类的析构函数中,自动释放外层类的资源
	class CRelease{
	public:
		~CRelease() { delete single; }
	};
	// 通过该静态对象在程序结束时自动析构的特点,来释放外层类的对象资源
	static CRelease release;
};
CSingleton* CSingleton::single = nullptr;
CSingleton::CRelease CSingleton::release;

int main(){
	CSingleton *p1 = CSingleton::getInstance();
	CSingleton *p2 = CSingleton::getInstance();
	CSingleton *p3 = CSingleton::getInstance();
	cout << p1 << " " << p2 << " " << p3 << endl;
	return 0;
}

是否线程安全?

复制代码
static CSingleton* getInstance(){
	if (nullptr == single){
		single = new CSingleton();
	}
	return single;
}

很明显,这个getInstance是个不可重入函数,也就它在多线程环境中执行,会出现竞态条件 问题,首先搞清楚这句代码,single = new CSingleton()它会做三件事情,开辟内存,调用构造函数,给single指针赋值,那么在多线程环境下,就有可能出现如下问题:

  1. 线程A先调用getInstance函数,由于single为nullptr,进入if语句
  2. new操作先开辟内存,此时A线程的CPU时间片到了,切换到B线程
  3. B线程由于single为nullptr,也进入if语句了,开始new操作

很明显,上面两个线程都进入了if语句,都试图new一个新的对象,不符合单例模式的设计,那该如何处理呢?对了,应该为getInstance函数内部加锁,在线程间进行互斥操作。此处使用锁+双重判断 ,也叫双重检验锁,代码如下:

复制代码
static CSingleton* getInstance(){
	if (nullptr == single){
		// 获取互斥锁
		pthread_mutex_lock(&mutex);
		// 这里需要再添加一个if判断,否则当两个线程都进入这里,又会多次new对象
		if(nullptr == single){
			single = new CSingleton();
		}
		// 释放互斥锁
		pthread_mutex_unlock(&mutex);
	}
	return single;
}
复制代码
#include <iostream>
using namespace std;

class CSingleton{
public:
	static CSingleton* getInstance(){
		static CSingleton single; // 懒汉式单例模式,定义唯一的对象实例
		return &single;
	}
private:
	static CSingleton *single;
	CSingleton() { cout << "CSingleton()" << endl; }
	~CSingleton() { cout << "~CSingleton()" << endl;}
	CSingleton(const CSingleton&);
};
int main(){
	CSingleton *p1 = CSingleton::getInstance();
	CSingleton *p2 = CSingleton::getInstance();
	CSingleton *p3 = CSingleton::getInstance();
	return 0;
}

对于static静态局部变量的初始化,编译器会自动对它的初始化进行加锁和解锁控制,使静态局部变量的初始化成为线程安全的操作,不用担心多个线程都会初始化静态局部变量,因此上面的懒汉单例模式是线程安全的单例模式!

相关推荐
永远睡不够的入25 分钟前
类和对象(下):流重载、初始化列表、友元
c++
忧郁的Mr.Li25 分钟前
设计模式--单例模式
javascript·单例模式·设计模式
范纹杉想快点毕业32 分钟前
状态机设计模式与嵌入式系统开发完整指南
java·开发语言·网络·数据库·mongodb·设计模式·架构
Trouvaille ~35 分钟前
【Linux】UDP Socket编程实战(四):地址转换函数深度解析
linux·服务器·网络·c++·udp·socket·地址转换函数
王老师青少年编程37 分钟前
2022信奥赛C++提高组csp-s复赛真题及题解:星战
c++·真题·csp·信奥赛·csp-s·提高组·星战
兩尛44 分钟前
2. 两数相加 c++
开发语言·c++
j445566111 小时前
C++中的备忘录模式
开发语言·c++·算法
近津薪荼1 小时前
dfs专题——二叉树的深搜3(二叉树剪枝)
c++·学习·算法·深度优先
卷卷的小趴菜学编程1 小时前
项目篇----仿tcmalloc的内存池设计(page cache)
c++·缓存·单例模式·tcmalloc·内存池·span cache
短剑重铸之日1 小时前
《设计模式》第十篇:三大类型之行为型模式
java·后端·设计模式·责任链模式·访问者模式·行为型模式