C++单例模式

C++单例模式

🌟🌟hello,各位读者大大们你们好呀🌟🌟

🚀🚀系列专栏:【C++的学习】

📝📝本篇内容:设计模式;单例模式;饿汉模式;懒汉模式

⬆⬆⬆⬆上一篇:特殊类的设计

💖💖作者简介:轩情吖,请多多指教(> •̀֊•́ ) ̖́-

1.设计模式

设计模式(Design Pattern)是一种在软件开发中,特别是在面向对象编程(OOP)中,用于解决常见设计问题的可复用解决方案。它并不是可以直接转换成代码的模板,而是一种设计思路或者经验总结,旨在提高代码的可读性、可维护性、可扩展性和重用性。我们的单例模式就是设计模式之一

2.单例模式

一个类只能创建一个对象,就是单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享

2.1.饿汉模式

单例模式一共分为两种,分别是饿汉模式和懒汉模式,我们先来讲一下饿汉模式,代码如下

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <thread>
#include <mutex>
using namespace std;
//饿汉模式
class Singleton
{
public:
	//使用static函数来返回能创建的唯一对象
	static Singleton* GetInstance()
	{
		return _instance;
	}
	
	//要保证线程安全
	void Add(const string& d)
	{
		mtx.lock();
		_data.push_back(d);
		mtx.unlock();
	}

	//读数据也会有线程安全问题
	void Print()
	{
		int cnt = 0;
		mtx.lock();
		for (auto& e : _data)
		{
			cnt++;
			cout << e << endl;
		}
		mtx.unlock();
		cout << "cnt=" << cnt << endl;
	}

private:
	Singleton()//将构造函数隐藏起来,保证不能随意创建对象
	{}

	//⭐⭐⭐这边要注意,需要把它给禁用掉,否则,可以通过*GetInstance来进行拷贝构造
	Singleton(const Singleton& sg) = delete;
	Singleton& operator=(const Singleton& sg) = delete;//保险起见也delete


private:
	vector<string> _data;
	//对象也要是static,这样才能让GetInstance返回
	static Singleton* _instance;
	mutex mtx;//保证在输入数据时的安全
};

Singleton* Singleton::_instance = new Singleton;


int main()
{
		srand((unsigned)time(nullptr));
	int n = 30;
	thread t1([n] {
		for (int i = 0; i < n; i++)
			Singleton::GetInstance()->Add("thread t1-" + to_string(rand() % 100 + 1));
		});

	thread t2([n] {
		for (int i = 0; i < n; i++)
			Singleton::GetInstance()->Add("thread t2-" + to_string(rand() % 100 + 1));
		});

	t1.join();
	t2.join();

	Singleton::GetInstance()->Print();
	return 0;
}

在上述代码中,我把相关的重要的点已经以注释的形式展现出来了
优点 :可以发现我们的代码,写的非常简单,其实这个就是饿汉模式的特点之一,同时如果这个单例对象在多线程并发环境下频繁使用,性能要求轻高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好
缺点 :如果单例对象特别的大,在我们的main前就要进行申请,这样就会造成一个问题,当代码中万一不需要使用,它会占用资源,程序启动的速度会受到影响,并且如果有多个单例类对象实例启动顺序不确定,懒汉可以做到

那接下来我们讲一下懒汉

2.2.懒汉模式

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <thread>
#include <mutex>
#include <ctime>
#include <cstdlib>
using namespace std;
//懒汉模式
class Singleton
{
public:
	static Singleton* GetInstance()
	{

		if (_instance == nullptr)//双重检测
		{
			//这边加上锁后,每次有线程进来时都会上锁,然后解锁,再返回;即使_instance
			//不为nullptr,这样效率就会大大降低,因此我们需要可以双重检测
			_mtx.lock();
			if (_instance == nullptr)
			{
				_instance = new Singleton;
			}
			_mtx.unlock();
		}
		return _instance;

	}

	//要注意线程安全
	void Add(const string& str)
	{
		_mtx.lock();
		_data.push_back(str);
		_mtx.unlock();
	}

	//要注意线程安全
	void Print()
	{
		int cnt = 0;//计算数量
		_mtx.lock();
		for (auto& e : _data)
		{
			cnt++;
			cout << e << endl;
		}
		_mtx.unlock();
		cout <<"cnt="<< cnt << endl;
	}

	~Singleton()
	{
		//析构函数可以用来做一些最后的写入工作
	}

	//garbage collection--垃圾回收
	struct GC
	{
		~GC()
		{
			DelInstance();
		}
	};

	//用来回收释放单例,必须使用static,这样才能保证在程序结束的时候,能够调用_gc的析构
	//不使用static的话,就是作为单例对象一部分,而单例还需要等待释放,而GC就是为了释放单例,就出现了蛋生鸡鸡生蛋问题
	static GC _gc;
	
	static void DelInstance()//这边也要保证是static,不然回收类无法使用
	{
		_mtx.lock();//这个也要保证线程安全
		if (_instance)
		{
			delete _instance;
			_instance = nullptr;//防止多次调用,而程序崩溃,设为nullptr后,只能delete一次

		}
		_mtx.unlock();
	}


private:
	Singleton()
	{}
	//⭐⭐⭐这边要注意,需要把它给禁用掉,否则,可以通过*GetInstance来进行拷贝构造
	Singleton(const Singleton& sg) = delete;
	Singleton& operator=(const Singleton& sg) = delete;//保险起见也delete
 
 
private:
	static Singleton* _instance;//用来接受创建的唯一对象
	vector<string> _data;//存数据
	//也得设为static,不然GetInstance中无法使用
	static mutex _mtx;
};

//懒汉是在使用时才创建,所以说这边先初始化为nullptr
Singleton* Singleton::_instance = nullptr;
mutex Singleton::_mtx;//表示初始化锁
int main()
{
	srand((unsigned)time(nullptr));
	int n = 30;
	thread t1([n] {
		for (int i = 0; i < n; i++)
			Singleton::GetInstance()->Add("thread t1-" + to_string(rand() % 100 + 1));
		});

	thread t2([n] {
		for (int i = 0; i < n; i++)
			Singleton::GetInstance()->Add("thread t2-" + to_string(rand() % 100 + 1));
		});

	t1.join();
	t2.join();

	Singleton::GetInstance()->Print();
	return 0;
}

具体代码如上,我们可以清楚的看到它比饿汉模式更复杂,但是它也不是一无是处,还是有优点的
优点 :第一次使用实例对象时才会创建对象,而不像饿汉模式是在main前创建,因此进程启动无负载;多个单例实例启动顺序自由控制
缺点 :比较复杂,其中设计double-check的方式加锁来保证效率和线程安全;并且内嵌垃圾回收类

一般全局都要使用单例对象,所以单例对象一般不需要显示释放

在这份代码中,其实很多细节,也能够加深对类的理解,比如我们类中定义了静态成员,其实我们可以把它们看成普通的静态变量,它们都是在程序结束时释放或者调用析构,因此我们的懒汉模式中_gc对象就是在程序结束时析构释放的,同时它调用了DelInstance函数,在DelIstance函数中释放了单例对象。

还有一个细节,就是我们的锁,对于它的构造函数是无参的,但是我们还是需要在类外进行初始化,不然相等于没有这个变量,因为类中只是声明

同样的还有一个点不知道大家有没有疑惑,那就是明明Singleton类的构造函数已经private了,我们的static单例指针还能调用new来构造?不要忘了这个static单例指针也是类中的,它只是在类外初始化罢了
🌸🌸C++单例模式的知识大概就讲到这里啦,博主后续会继续更新更多C++的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪

相关推荐
漂流瓶66666614 分钟前
Scala的模式匹配变量类型
开发语言·后端·scala
夏天吃哈密瓜19 分钟前
Scala中的正则表达式01
大数据·开发语言·后端·正则表达式·scala
2401_8337880521 分钟前
Scala的模式匹配(2)
java·开发语言
Lbs_gemini060321 分钟前
C++研发笔记14——C语言程序设计初阶学习笔记12
c语言·开发语言·c++·笔记·学习
ac-er88881 小时前
GD库如何根据颜色生成纯色背景图
开发语言·php
悠悠龙龙2 小时前
框架模块说明 #05 权限管理_03
java·开发语言·spring
开心羊咩咩3 小时前
Idea 2024.3 突然出现点击run 运行没有反应,且没有任何提示。
java·ide·intellij-idea
waterme1onY3 小时前
IDEA中MAVEN的一些设置问题
java·maven·intellij-idea
阿华的代码王国3 小时前
【算法】——前缀和(矩阵区域和详解,文末附)
java·开发语言·算法·前缀和
我的老子姓彭3 小时前
C++学习笔记
c++·笔记·学习