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

相关推荐
Algorithm15767 分钟前
JVM是什么,与Java的关系是什么,以及JVM怎么实现的跨平台性
java·开发语言·jvm
Gnevergiveup8 分钟前
2024网鼎杯青龙组Web+Misc部分WP
开发语言·前端·python
LaoZhangGong12321 分钟前
使用常数指针作为函数参数
c++·经验分享
边疆.22 分钟前
C++类和对象 (中)
c语言·开发语言·c++·算法
yy_xzz24 分钟前
QT编译报错:-1: error: cannot find -lGL
开发语言·qt
你不讲 wood27 分钟前
使用 Axios 上传大文件分片上传
开发语言·前端·javascript·node.js·html·html5
林浔090635 分钟前
C语言部分输入输出(printf函数与scanf函数,getchar与putchar详解,使用Linux ubuntu)
c语言·开发语言
一颗甜苞谷1 小时前
开源一款基于 JAVA 的仓库管理系统,支持三方物流和厂内物流,包含 PDA 和 WEB 端的源码
java·开发语言·开源
CLCNboss1 小时前
Mac安装Ruby
开发语言·经验分享·笔记·macos·ruby
ai产品老杨1 小时前
深度学习模型量化原理
开发语言·人工智能·python·深度学习·安全·音视频