【C++】设计模式 — 从零开始认识单例模式

news2024/9/20 1:56:58

在这里插入图片描述

人的一生本来就是一场有来无回的冒险。
--- priest 《残次品》---

设计模式 — 单例模式

  • 1 设计模式
  • 2 单例模式
    • 2.1 饿汉模式
    • 2.2 懒汉模式
  • 3 总结

1 设计模式

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。设计模式也是类似,是代代相传的智慧!

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样

我们在之前其实也使用过一些设计模式:迭代器模式,适配器模式。今天我们来学习一个新的的设计模式:单例模式。

2 单例模式

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

比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。后面即将开始实践的高并发内存池项目也是使用的单例模式!

单例模式的实现方式有两种:饿汉模式和懒汉模式。

2.1 饿汉模式

饿汉模式就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象
假如我们有下面这样一个类

class ConfigInfo
{
public:

private:
	string _ip = "127.0.0.1";
	int _port = 80;
	//...
};

为了保证单例,就不能允许用户可以显式构造对象,也不能允许拷贝构造和赋值重载!所以我们把这些构造函数私有化,把拷贝构造和赋值重载给delete掉!

private:
	ConfigInfo()
	{}
	ConfigInfo(const ConfigInfo&) = delete;
	ConfigInfo operator=(const ConfigInfo&) = delete;

这样我们就需要单独写一个获取对象的接口,让用户可以进行获取对象。

最重要的来了,我们然后保证只能创建一个对象呢?显然在类外肯定是不可能的的,类外我们无法保证只能创建一个对象,但是在类里我们加入一个静态类对象声明(普通的类对象是不能成为自身类的成员的),这样该类就只能创建当前一个对象!我们在进入main函数之前就进行初始化,在主函数内不在进行创建工作。

class ConfigInfo
{
public:
	static ConfigInfo* GetInstance()
	{
		return &_sInfo;
	}
	//...
	//其他接口
	//...
private:
	ConfigInfo()
	{}
	ConfigInfo(const ConfigInfo&) = delete;
	ConfigInfo operator=(const ConfigInfo&) = delete;
private:
	string _ip = "127.0.0.1";
	int _port = 80;
	//...
	//声明
	static ConfigInfo _sInfo;
};

饿汉模式的实现是很简单,但是饿汉模式也是缺点的:

  1. 如果需要很多单例类,并且有些单例类的初始化资源很多,那么就会导致很长时间才能进入到main函数,给用户的体验不是很好!
  2. 如果两个类有初始化依赖关系:A,B类是两个单例类,B类进行连接数据库,A类中包含B类,所以初始化完成的顺序会导致程序可能出现问题!

2.2 懒汉模式

懒汉模式是第一次调用到单例类时才进行创建单例对象!

如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好!

缺点就是实现起来有点复杂!

class ConfigInfo
{
public:
	static ConfigInfo* GetInstance()
	{
		//获取对象时创建静态变量
		static ConfigInfo _sInfo;
		return &_sInfo;
	}
private:
	ConfigInfo()
	{}
	ConfigInfo(const ConfigInfo&) = delete;
	ConfigInfo operator=(const ConfigInfo&) = delete;
private:
	string _ip = "127.0.0.1";
	int _port = 80;
	//...
};

我们需要获取对象函数中进行静态对象的初始化,在C++11之前,局部的static对象构造是有线程安全风险的!多线程的情况下,都调用GetInstance()是会造成初始化失败。现在基本所有的编译器都支持C++11所以不用担心这个问题了!

C++98是通过一个指针来进行懒汉模型:

  1. 像恶汉模式一样,设置一个成员指针变量static ConfigInfo* _sInfo;。在进入主函数之前初始化为nullptr!
  2. GetInstance()时,判断此时_sInfo是否为空指针,如果是空指针就进行开辟空间,反之就直接返回指针!
class ConfigInfo
{
public:
	static ConfigInfo* GetInstance()
	{
		//获取对象时开辟空间
		if (_sInfo == nullptr)
		{
			_sInfo = new ConfigInfo;
		}
		return _sInfo;
	}
private:
	ConfigInfo()
	{}
	ConfigInfo(const ConfigInfo&) = delete;
	ConfigInfo operator=(const ConfigInfo&) = delete;
private:
	string _ip = "127.0.0.1";
	int _port = 80;
	//...
	static ConfigInfo* _sInfo;
};

但是这样其实是有风险的!开辟空间的代码显然是临界区!==判断和new操作不是原子的!所以我们是要进行加锁的!我们新加一个静态锁成员变量 ,在主函数之前进行定义!

private:
	string _ip = "127.0.0.1";
	int _port = 80;
	//...
	static ConfigInfo* _sInfo;
	static mutex _mtx;

然后使用锁守卫来进行进行管理锁:

	static ConfigInfo* GetInstance()
	{
		//RAII管理锁
		unique_lock<mutex> lock(_mtx);
		//获取对象时创建静态变量
		if (_sInfo == nullptr)
		{
			_sInfo = new ConfigInfo;
		}
		return _sInfo;
	}

这样就线程安全了,但是现在这个代码是有小问题的,每次进入这个获取对象的对象,都会进行上锁。但是在已经创建了对象,再次获取对象的时候其实并不需要进行上锁!锁只需要保护第一次创建对象!在后续的调用中频繁上锁会导致性能下降。为了解决这个问题,我们一般使用双检查机制:

	static ConfigInfo* GetInstance()
	{
		//为了保证性能,进行一次初步的检查
		if (_sInfo == nullptr)
		{
			//为空才进行上锁来保证线程安全!
			unique_lock<mutex> lock(_mtx);
			//获取对象时创建静态变量
			if (_sInfo == nullptr)
			{
				_sInfo = new ConfigInfo;
			}
		}
		
		return _sInfo;
	}

对于_sInfo的释放,可以不用管,进程结束会统一释放。也可以设计一个垃圾回收类,在生命周期到的时候会调用垃圾回收的析构函数,在这个析构函数中进行_sInfo空间的释放就可以了!

3 总结

单例模式是一个很实用的设计模式,单例类只能创建一个对象,像高并发内存池这样的类,全局只有一个就够了。单例类的底层实现有两种方法:饿汉模式和懒汉模式。他们都是通过静态成员变量来保证只有一个单例。

  1. 饿汉模式是将构造函数私有化,并且不允许拷贝构造和赋值重载。在进行主函数之前就完成对象的构建!如果单例类对象太多,会造成进入主函数过慢!
  2. 懒汉模式是在第一次调用的时候完成构造对象。懒汉模式的实现比较复杂!

在实际生产中,灵活使用这两个实现方法可以帮助我们解决很多复杂问题!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2038545.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

YZ系列工具之YZ05:代码运行中调用“计算器”使用说明

我给VBA下的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套一部VBA手册&#xff0c;教程分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的…

语音播报加入预警系统

语音播报加入预警系统 引言 引入语音警报 我们前一章, 已经把jq8900-16p模块, 单独进行了测试, 可以通过发送命令, 让模块播报设定好的声音。那么语音播报&#xff0c; 在预警系统中&#xff0c; 也必不可少&#xff0c; 我们现在有了led灯光警报,如果主人在睡觉, 是不能及时…

redis列表若干记录

2、列表 ziplist ziplist参数 entry结构 entry-data:节点存储的元素prelen&#xff1a;记录前驱节点长度encoding&#xff1a;当前节点编码格式encoding encoding属性 使用多个子节点存储节点元素长度&#xff0c;这种多字节数据存储在计算机内存中或者进行网络传输的时的字节…

排序算法——插入排序

一、插入排序概念 直接插入排序&#xff08;Insertion Sort&#xff09;是一种简单的排序算法&#xff0c;它的工作原理类似于人们手动排序卡片的方式。该算法通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插…

ubuntu、cpolar、api开启映射之路

1.国内cpolar安装 curl -L https://www.cpolar.com/static/downloads/install-release-cpolar.sh | sudo bash或 cpolar短链接安装方式&#xff1a;(国外使用&#xff09; curl -sL https://git.io/cpolar | sudo bash2.查看版本号&#xff0c;正常显示即为安装成功 cpolar …

HR系统怎么选?2024年10大热门工具评测

本文中介绍的工具有&#xff1a;Moka、名才MCHR、HiHR、华天动力HRM、红海eHR、易路eRoad、宏景HJSOFT、Gusto、Zenefits、BambooHR。 在当今竞争激烈的商业环境中&#xff0c;找到一个适合企业的HR系统可能是一个令人头疼的问题。市面上的HR工具琳琅满目&#xff0c;各有千秋&…

Django 自定义用户 VS 用户资料

Django是一个流行的Web框架&#xff0c;它提供了一套完整的用户认证系统&#xff0c;其中包括内置的User模型用于存储基本的用户信息&#xff0c;如用户名、密码等。然而&#xff0c;如果我们需要更详细的用户资料管理&#xff0c;比如添加更多的字段或者自定义验证规则&#x…

Linux 常见的冷知识集锦

一、前言 本文旨在记录那些常见的Linux概念和名词&#xff0c;但这些又没经常直接使用到&#xff0c;更多在底层运行&#xff0c;见过却又不是特别清楚的碎片知识&#xff0c;以温故知新。 二、知识点和概念说明 2.1、POSIX标准/协议 POSIX&#xff08;Portable Operating S…

股票技术指标 RSI KDJ MACD

具体指标解释&#xff0c;大模型都有&#xff0c;只说作用 RSI&#xff08;Relative Strength Index&#xff0c;相对强弱指数&#xff09; 超买和超卖水平&#xff1a;通常情况下&#xff0c;RSI值超过70表示市场可能超买&#xff0c;而低于30表示可能超卖。这并不意味着价格…

数据结构:顺序二叉树(堆)

目录 前言 一、堆的实现 1.1 头文件 1.2 堆的初始化及销毁 1.3 堆的插入 1.4 堆的删除 1.5 取堆顶数据和判空 前言 前面我们讲了二叉树有顺序结构和链式结构&#xff0c;今天就来讲一下顺序结构 普通的二叉树是不适合用数组来存储的&#xff0c;因为可能会存在大量的空间…

HTTP请求的流转路径,从Tomcat到SpringMVC

本文主要讲一下&#xff0c;一个HTTP请求在后端服务的流转路径&#xff0c;Tomcat等一众servlet容器如何定义了Web应用的基础样貌&#xff0c;后来的MVC框架又是如何弱化了servlet的存在&#xff0c;改为自己实现请求派发的。 前些日子我写了十几篇文章来介绍Tomcat的架构&…

AI在医学领域:联邦学习 (FL) 在肿瘤学的应用综述

关键词&#xff1a;联邦学习 (Federated Learning, FL)、机器学习 (Machine Learning, ML)、肿瘤学 (Oncology)、数据隐私 (Data Privacy)、精准医疗 (Precision Medicine)、多模态 (Multi-modal) 肿瘤学正在经历快速的变革&#xff0c;这得益于机器学习&#xff08;ML&#xf…

tinymce字体为48px后再设置numlist数字列表导致前面的序号字体不对--【已解决】

问题描述&#xff1a; tinymce选择完大号字体&#xff0c;如48px&#xff0c;再选择数字列表&#xff0c;会导致数字列表的序号字体不对。 解决&#xff1a; 演示效果

【C语言篇】C语言常考及易错题整理DAY3

文章目录 选择题整形提升与算术转换左移右移操作符操作符优先级与结合性后置指针变量基本知识 编程题最大连续1的个数完全数计算单词倒排面试题.珠玑妙算两数之和 选择题 整形提升与算术转换 声明以下变量&#xff0c;则表达式: ch/i (f*d – i) 的结果类型为&#xff08; &…

Fal.ai Flux 1-Pro/Viva.ai/哩布哩布AI:AI绘图部分免费工具+原图提示词Prompt

目录 #1 找软件 #2 懂提示词 #3 更难的一步&#xff0c;会英文 我个人认为&#xff0c;想要玩文生图&#xff0c;你要会3个步骤&#xff1a; #1 找软件 主流文生图软件&#xff1a;Midjourney、Stable Diffusion、Dall-E 3 巧了&#xff0c;我用的都是小众、免费的画笔工…

Linux 错误码

目录 一、概述二、含义三、错误处理函数1、IS_ERR2、strerr、perror 一、概述 在 Linux 系统中&#xff0c;错误码是用来表示操作系统运行过程中发生的错误的数字代码。错误码通常由负数表示&#xff0c;0 表示成功&#xff0c;正数表示警告或其他非致命错误。 为了开发者更好…

查询大数据信用需要收费吗?哪个平台好一点?

随着大数据技术被运用到金融行业&#xff0c;不少申贷人都开始了解自己的大数据信用&#xff0c;在查询大数据信用的时候&#xff0c;查询大数据信用需要钱吗?哪个平台好一点?等问题是很多人都比较关心的问题&#xff0c;下面本文就详细为大家详解一下&#xff0c;希望对你了…

基于python的百度迁徙迁入、迁出数据分析(九)

副标题&#xff1a;从百度迁徙数据看——人口虹吸效应 人口虹吸效应&#xff1a;人口虹吸效应是指大城市或中心城市因其经济、文化、教育、医疗等资源的优势&#xff0c;吸引周边地区的人口、资本和其他资源向其集中的一种现象。这种效应在城市化进程中尤其明显&#xff0c;通…

公司起诉员工泄密难吗?如何搜寻有力的证据?专业审计软件助力,追责之路其实不难!

在企业管理中&#xff0c;员工泄密是一个严重的问题&#xff0c;不仅可能损害企业的商业利益&#xff0c;还可能对企业的声誉造成不可挽回的影响。然而&#xff0c;公司起诉员工泄密并非易事&#xff0c;需要满足严格的法律条件和程序&#xff0c;并面临证据收集与举证、法律程…

10款好用的文件加密软件排行榜,2024企业常用的文件加密软件

在数据安全日益受到重视的今天&#xff0c;文件加密软件已成为保护企业敏感信息的重要工具。以下是2024年企业常用的10款好用的文件加密软件排行榜&#xff0c;帮助你选择适合的工具来保护你的文件和数据。 1. 安秉加密软件 安秉加密软件提供用户友好的界面和强大的加密功能。…