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

相关推荐
Monly215 分钟前
Java(若依):修改Tomcat的版本
java·开发语言·tomcat
boligongzhu6 分钟前
DALSA工业相机SDK二次开发(图像采集及保存)C#版
开发语言·c#·dalsa
Eric.Lee20216 分钟前
moviepy将图片序列制作成视频并加载字幕 - python 实现
开发语言·python·音视频·moviepy·字幕视频合成·图像制作为视频
小俊俊的博客7 分钟前
海康RGBD相机使用C++和Opencv采集图像记录
c++·opencv·海康·rgbd相机
7yewh8 分钟前
嵌入式Linux QT+OpenCV基于人脸识别的考勤系统 项目
linux·开发语言·arm开发·驱动开发·qt·opencv·嵌入式linux
waicsdn_haha20 分钟前
Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
java·运维·服务器·开发语言·windows·后端·jdk
_WndProc22 分钟前
C++ 日志输出
开发语言·c++·算法
薄荷故人_23 分钟前
从零开始的C++之旅——红黑树及其实现
数据结构·c++
m0_7482400224 分钟前
Chromium 中chrome.webRequest扩展接口定义c++
网络·c++·chrome
qq_4335545431 分钟前
C++ 面向对象编程:+号运算符重载,左移运算符重载
开发语言·c++