当前位置: 首页 > news >正文

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

在这里插入图片描述

人的一生本来就是一场有来无回的冒险。
--- 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. 懒汉模式是在第一次调用的时候完成构造对象。懒汉模式的实现比较复杂!

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【Redis】主从复制
  • 【Qt】QPluginLoader 类学习
  • 【社区团购技术实现】
  • 【问题】容器部署场景Spring Bean偶尔循环依赖问题
  • 71、docker网络
  • 白骑士的Matlab教学高级篇 3.1 高级编程技术
  • 聊聊场景及场景测试
  • [Meachines] [Medium] Haircut Curl命令注入+TRP00F自动化权限提升+Screen4.5.0权限提升
  • C语言类型转换的问题
  • 数据结构----队列
  • RabbitMq消息队列(缓存加速)
  • 登录过程记录
  • 讲解 狼人杀中的买单双是什么意思
  • php 在app中唤起微信app进行支付,并处理回调通知
  • mysql误删数据恢复记录
  • 《网管员必读——网络组建》(第2版)电子课件下载
  • 0基础学习移动端适配
  • bearychat的java client
  • Java方法详解
  • js算法-归并排序(merge_sort)
  • LeetCode18.四数之和 JavaScript
  • Material Design
  • PAT A1092
  • ReactNativeweexDeviceOne对比
  • Vue2.0 实现互斥
  • 给Prometheus造假数据的方法
  • 如何打造100亿SDK累计覆盖量的大数据系统
  • 如何邀请好友注册您的网站(模拟百度网盘)
  • 系统认识JavaScript正则表达式
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • #Datawhale AI夏令营第4期#AIGC文生图方向复盘
  • #Ubuntu(修改root信息)
  • $forceUpdate()函数
  • (WSI分类)WSI分类文献小综述 2024
  • (论文阅读23/100)Hierarchical Convolutional Features for Visual Tracking
  • (五)Python 垃圾回收机制
  • (一)使用Mybatis实现在student数据库中插入一个学生信息
  • ***详解账号泄露:全球约1亿用户已泄露
  • .net 8 发布了,试下微软最近强推的MAUI
  • .Net Core 生成管理员权限的应用程序
  • .NET Core 中插件式开发实现
  • .NET 使用配置文件
  • .NET6 开发一个检查某些状态持续多长时间的类
  • .NET轻量级ORM组件Dapper葵花宝典
  • .NET正则基础之——正则委托
  • /var/spool/postfix/maildrop 下有大量文件
  • @select 怎么写存储过程_你知道select语句和update语句分别是怎么执行的吗?
  • [\u4e00-\u9fa5] //匹配中文字符
  • [20140403]查询是否产生日志
  • [AI]文心一言出圈的同时,NLP处理下的ChatGPT-4.5最新资讯
  • [Algorithm][动态规划][简单多状态DP问题][按摩师][打家劫舍Ⅱ][删除并获得点数][粉刷房子]详细讲解
  • [Android] Amazon 的 android 音视频开发文档
  • [android] 手机卫士黑名单功能(ListView优化)
  • [android学习笔记]学习jni编程
  • [C#]winform部署PaddleOCRV3推理模型