【C++】单例模式

1.什么是单例模式

单例模式:顾名思义,这个对象是全局的,且只有唯一的一个实例(不能创建出两份)。

用途:多个线程同时访问一个全局资源时可能会用到。例如,在C++的内存管理中,内存池只有一份,但是会有多个线程访问内存池,那么内存池的设计就可能需要单例模式。所以单例模式还是比较重要的。

2.单例模式核心设计方式

2.1饿汉模式

举例:

cpp 复制代码
#include <iostream>
#include <string>
using std::cout;
using std::endl;
//配置信息类
//饿汉模式:一开始(在main函数前)就创建对象
class ConfigInfo {
public:
	static ConfigInfo* GetInstance() {
		return &_sInfo;
	}
	std::string GetIp() {
		return _ip;
	}
	void SetIp(const std::string& ip) {
		_ip = ip;
	}
private:
	ConfigInfo()
	{}
	ConfigInfo(const ConfigInfo&) = delete;
	ConfigInfo& operator=(const ConfigInfo&) = delete;
private:
	std::string _ip = "127.0.0.1";
	int _port = 80;
	//声明
	static ConfigInfo _sInfo;
};

//定义
ConfigInfo ConfigInfo::_sInfo;


int main() {
	cout << ConfigInfo::GetInstance() << endl;
	cout << ConfigInfo::GetInstance() << endl;

	ConfigInfo::GetInstance()->SetIp("192.33.3.22");
	cout << ConfigInfo::GetInstance()->GetIp() << endl;
	return 0;
}

饿汉模式:核心设计

1.首先把配置信息类的构造,拷贝构造,赋值重载全部设置为私有,这样就不能在类外面创建对象。

2.在私有成员中,声明一个静态的类对象,然后在类外面定义,(相当于成员函数的声明和定义分离,能够访问到私有的构造函数)这样就可以保证在全局只有唯一一个实例化的对象。(静态的成员变量不属于类,它只受类域的限制,它实际上是定义在静态区的。如果把static关键字去掉,就会"无限套娃",是错误的做法)。

3.给一个公有的静态函数:GetInstance(),这样可以通过该函数得到对象的地址或引用从而进行更多的操作。因为是静态的,所以可以通过类名访问,更方便。

饿汉模式的问题:

问题1:

如果很多单例类都是饿汉模式,有些单例对象初始化资源很多,导致程序启动慢。迟迟进不了main函数。

问题2:

如果两个单例类有初始化依赖关系,饿汉模式也无法解决。

比如A类和B类是单例,A单例要链接数据库,B单例要用A单例访问数据库。但是A和B谁先初始化不知道,这时就有问题。

2.2懒汉模式(C++11之后)

cpp 复制代码
#include <iostream>
#include <string>
using std::cout;
using std::endl;
//配置信息类
//懒汉模式:在调用时创建对象
class ConfigInfo {
public:
	static ConfigInfo* GetInstance() {
		//局部静态单例对象
		static ConfigInfo info;
		return &info;
	}
	std::string GetIp() {
		return _ip;
	}
	void SetIp(const std::string& ip) {
		_ip = ip;
	}
private:
	ConfigInfo()
	{
		cout << "ConfigInfo" << endl;
	}
	ConfigInfo(const ConfigInfo&) = delete;
	ConfigInfo& operator=(const ConfigInfo&) = delete;
private:
	std::string _ip = "127.0.0.1";
	int _port = 80;
	
};




int main() {
	cout << ConfigInfo::GetInstance() << endl;
	cout << ConfigInfo::GetInstance() << endl;

	ConfigInfo::GetInstance()->SetIp("192.33.3.22");
	cout << ConfigInfo::GetInstance()->GetIp() << endl;

	
	return 0;
}

与饿汉模式相比,懒汉模式就是把静态私有成员改成了GetInstance()中的静态局部对象,这样该对象在调用时只会构造一次。这样就很好的解决了饿汉模式的问题。

但这样写也有一个小问题,就是在C++11之前无法保证GetInstance()是线程安全的,也就是说当两个线程同时调用该函数时,可能会构造两次。但在C++11之后,编译器经过特殊处理了,就没问题了。如果你的编译器不支持C++11就不能用这种写法。

2.3懒汉模式(C++11之前)

cpp 复制代码
#include <iostream>
#include <string>
#include <mutex>
using std::cout;
using std::endl;
//配置信息类
//懒汉模式:在调用时创建对象
class ConfigInfo {
public:
	static ConfigInfo* GetInstance() {
		//多线程调用需要考虑线程安全问题
		//双检查机制
		if (_spInfo == nullptr) {  //提高性能
			std::unique_lock<std::mutex> lock(_mtx);
			if (_spInfo == nullptr) {  //线程安全
				_spInfo = new ConfigInfo;
			}
		}
		return _spInfo;
	}
	std::string GetIp() {
		return _ip;
	}
	void SetIp(const std::string& ip) {
		_ip = ip;
	}
private:
	ConfigInfo()
	{
		cout << "ConfigInfo" << endl;
	}
	ConfigInfo(const ConfigInfo&) = delete;
	ConfigInfo& operator=(const ConfigInfo&) = delete;
private:
	std::string _ip = "127.0.0.1";
	int _port = 80;
	
	static ConfigInfo* _spInfo;
	static std::mutex _mtx;
};
ConfigInfo* ConfigInfo::_spInfo = nullptr;
std::mutex ConfigInfo::_mtx;



int main() {
	cout << ConfigInfo::GetInstance() << endl;
	cout << ConfigInfo::GetInstance() << endl;

	ConfigInfo::GetInstance()->SetIp("192.33.3.22");
	cout << ConfigInfo::GetInstance()->GetIp() << endl;

	
	return 0;
}

如果你的编译器支持C++11,就可以不考虑这种写法,因为这种写法更复杂:

声明静态的对象指针和静态的锁并初始化,因为初始化一个指针的开销可以忽略不计。然后在GetInstance()函数中采用双检查机制,第一次检查是为了提高性能,避免每次调用该函数时都会上锁。第二次检查是为了保证线程安全,确保只创建一个对象。

关于内存泄漏问题:

因为new出来的对象往往要考虑释放的问题,但是单例模式有所不同。单例模式的目的就是确保全局只有唯一的一个对象,以后就算不用了没有释放也影响不大(只有一个对象,占用内存基本可以忽略,等进程结束后会自动回收)。如果真的有特殊需求,可以查阅相关资料来释放该对象(因为不建议这么做,本文就不介绍了)。

相关推荐
学习前端的小z3 分钟前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
神仙别闹11 分钟前
基于C#和Sql Server 2008实现的(WinForm)订单生成系统
开发语言·c#
XINGTECODE12 分钟前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
我们的五年21 分钟前
【Linux课程学习】:进程程序替换,execl,execv,execlp,execvp,execve,execle,execvpe函数
linux·c++·学习
zwjapple29 分钟前
typescript里面正则的使用
开发语言·javascript·正则表达式
小五Five30 分钟前
TypeScript项目中Axios的封装
开发语言·前端·javascript
前端每日三省32 分钟前
面试题-TS(八):什么是装饰器(decorators)?如何在 TypeScript 中使用它们?
开发语言·前端·javascript
凡人的AI工具箱1 小时前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
做人不要太理性1 小时前
【C++】深入哈希表核心:从改造到封装,解锁 unordered_set 与 unordered_map 的终极奥义!
c++·哈希算法·散列表·unordered_map·unordered_set
程序员-King.1 小时前
2、桥接模式
c++·桥接模式