c++设计模式之单例设计模式

news2024/10/2 6:31:13

💂 个人主页:[pp不会算法v](https://blog.csdn.net/weixin_73548574?spm=1011.2415.3001.5343)
🤟 版权: 本文由【pp不会算法^v^】原创、在CSDN首发、需要转载请联系博主
💬 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦

c++设计模式系列文章

什么是单例设计模式


顾名思义,所谓单例设计模式,就是在这个应用程序中某个类只有一个实例,程序中的所有模块共用这个模块

举几个经常用到单例设计模式的应用场景:
1、网络编程的时候我们将socket,各种网络操作...封装成一个类,那么这个类一般只有一个实例
2、数据库封装类,这个一般也是只有一个实例
3、我上次使用javafx开发一个IM项目的时候对于好友列表这个gui控件(自定义的)也用到了单例设计模式,以为整个应用程序就这一个好友列表
..................剩下的大家可以根据自己的开发经验分享在评论区


什么是单例设计模式中的饿汉式和懒汉式


<font color="red">饿汉式:</font>程序一开始就创建这个实例

<font color="red">懒汉式:</font>第一次调用getInstance()的时候才创建这个实例

其实这个很好记:饿汉他很饥饿,可定很急着去吃东西,所以才急着我还没有去主动获取,他就在程序开始的时候创建实例了;懒汉很懒,所以她一点都不急,不主动,那么只有我们主动找他要的时候太才会创建实例

 c++中单例设计模式的实现

内部类析构


因为这个类只有一个实例那么我们就要防止在外部去创建这个类的实例(对象)就要私有化这个类的构造函数
又因为要保证这个实例不会被外部显示析构,我们还要将析构函数私有化


Single.h:
class Single{
public:
static Single*getSingleInstance()
{
if(m_instance==NULL)
m_instance=new Single();
return m_instance;
}
private:
Single(){}
Single(const Single&);
Single& operator=(const Single&);
~Single(){}
static void deleteSingleInstance()
{
if(m_instance!=NULL)
{
Single*instance=m_instance;
m_instance=NULL;
delete instance;
}
}
class Deletor{
public:
Deletor()
{
//这里你可以创建这个类的实例,也可以不创建,创建那么就是饿汉式,不创建就是懒汉式
}
~Deletor()
{
deleteSingleInstance();
}
}
static Single*m_instance;
static Deletor deletor;
}
Single.cpp:
Single* Single::m_instance=NULL;
Single::Deletor Single:: deletor;


 

这里的Deletor的静态对象deletor就解决了内存泄漏的问题,这个内部类对象外部也访问不到,而它又是一个静态的变量,我们知道c++应用程序在 结束之前会逆序析构静态变量,而这个类的析构是public那么就能自动析构,析构的时候就释放了单例的堆内存,然后你将这个类的一些操作封装在public下,那么这个单例就已经不需要你管了,你要用的时候获取它的指针就行了,

 智能指针析构


上面这种使用内部类的方式看着有一丢丢麻烦,而且不是很优雅
c++给我们提供了一个很好用的工具:智能指针

智能指针简介
更加具体的介绍请自行去查阅资料,这里面大有文章


C++ 智能指针是一种用于管理动态分配的对象内存的工具,可以帮助避免内存泄漏和悬空指针等问题。C++11 引入了两种主要类型的智能指针:std::shared_ptr 和 std::unique_ptr。

std::shared_ptr:代表一个共享所有权的智能指针。多个 shared_ptr 对象可以同时拥有一个对象,并在不再需要时自动释放对象内存。它使用引用计数的方式来追踪有多少个 shared_ptr 共享同一对象的所有权。当引用计数减为零时,对象内存将被释放。
#include <memory>
std::shared_ptr<Type> sharedPtr = std::make_shared<Type>(args);

std::unique_ptr:代表一个独占所有权的智能指针。只能有一个 unique_ptr 指向一个对象,不能进行拷贝构造或赋值操作。当 unique_ptr 被销毁或重置时,它所拥有的对象内存会被释放。
#include <memory>
std::unique_ptr<Type> uniquePtr = std::make_unique<Type>(args);


通过使用智能指针,可以避免显式地调用 delete 或 delete[] 来释放动态分配的对象内存,从而减少内存泄漏的风险。可以使用智能指针提供的成员函数和操作符来访问和操作所拥有的对象。另外,还可以自定义删除器函数来指定动态释放对象内存的方式。

除了 shared_ptr 和 unique_ptr,C++ 还提供了 std::weak_ptr、std::auto_ptr(C++11 之前的版本)等其他类型的智能指针,每种都有不同的所有权和语义特点,适用于不同的场景。选择合适的智能指针类型可以提高代码的安全性和可维护性。


那么我们就用智能指针来修改上面使用内部类的方式实现的单例设计模式
 

Single.h:
#include <memory>//使用智能指针要包含的头文件
class Single{
public:
static Single*getSingleInstance()
{
    if (m_instance == NULL)
        m_instance = std::make_unique<Single>();
    return m_instance.get();
}
private:
Single(){}
Single(const Single&);
Single& operator=(const Single&);
~Single(){}
static std::unique_ptr<Single> m_instance;
}
Single.cpp:
std::unique_ptr<Single> Single::m_instance=NULL;


单例模式的线程安全问题


首先我们要知道单例模式什么时候会出现线程安全问题,其实就只有一个时刻,就是创建实例的时候,创建之后就不存在线程安全的问题

首先最简单的避免这个问题的方法就是饿汉式,在只有主线程的时候就创建了实例,也就不存在线程安全问题了

但是怎么解决懒汉式的线程安全问题呢?

当实例未创建的时候多个线程同时去请求这个实例,那么都通过了=NULL的判断,那么就会重复创建实例,为了解决这个问题我罗列处一下几种处理方法

1、一次判断+互斥锁

Single*getInstance()
{
//加锁
mutex.lock();
if(m_instance==NULL)
m_instance=new Single();
//解锁
mutex.unlock();
return m_instance;
}

2、双重检测锁模式(DCL:Double-Checked Locking Pattern

Single* getInstance()
{
if(m_instance==NULL)
  {
    mutex.lock();

    if(m_instance==NULL)
    m_instance=new Single();

    mutex.unlock();
  }
return m_instance;
}

为什么要检测两次呢,相较于第一种方法这不是多次一举吗,开始我看到也是这么认为的,但是后来一想,线程安全只会在创建实例的时候出现第一种方法就算创建之后多线程同时获取实例的时候也会产生竞争产生等待那么是不是除了创建实例的时候的等待是有意义的后面的等待都是多余的了纯纯是消耗性能了,再看这个DCL机制,当我们创建完成之后还会等待吗,根本不会因为第一次检测就通过了,直接返回实例的指针了,不会进入锁的区域,但是没创建的时候就会竞争等待,这样就解决了第一种方式的弊端

3、原子操作

思考一下第二种方式还有没有潜在的问题存在呢?

有的,c++中new一个对象的全过程:

在 C++ 中,new 一个对象的过程确实可以分为三个阶段:分配内存空间、初始化成员变量和调用构造方法。

  1. 分配内存空间:在使用 new 运算符时,会首先从堆(Heap)中分配一块合适大小的内存空间用于存储对象的数据。

  2. 初始化成员变量:在分配内存空间后,编译器会根据对象的定义,对对象的成员变量进行初始化。这包括调用各个成员变量的默认构造函数或者使用初始化列表进行初始化。

  3. 调用构造方法:在成员变量初始化完成之后,会调用对象的构造方法,对对象进行进一步的初始化操作。构造方法是类的特殊成员函数,用于完成对象的初始化工作。

在上述过程中,当分配内存空间完成后,对象的指针就不再为空了。具体来说,当成功分配内存空间并返回指针时,该指针指向的内存地址即为对象的有效地址,可以开始对对象进行访问和操作。

那么问题就来了,可能一个线程拿到了锁开始new出实例但是还只完成了第一步分配内存空间,但是此时m_instance就已经不为NULL了,那么别的线程如果此时获取实例那么第一次检测就通过了那么得到的实例就是没有完成初始化和构造的实例这就会造成严重的错误甚至导致程序崩溃

所以我们要保证要么就彻底创建实例要么就不创建,这就涉及到原子操作了                                                               v         

atomic<Single*> Single::m_instance;//记得在源文件初始化这里我就不写了

getInstance() {
    if (m_instance == NULL) { 
        mutex.lock();
        if (pInstance == nullptr) { 
            pInstance = new Widget(); 
        }
        mutex.unlock();
    } 
    return m_instance;
}

但是上面再判断的时候使用原子类型也会进行原子操作但是这是没必要的我们只需要在创建实例的时候进行原子操作就行了所以可以优化一下:


Single* getInstance() {
    Single* p = m_instance;
    if (p == nullptr) { 
        mutex.lock();
        if ((p = pInstance) == NULL) { 
            m_instance= p = new Single(); 
        }
        mutex.unlock();
    } 
    return p;
}

参考了很多别人的文章,如果有不对的还请指正,如果后面有更好的方法再补充吧....

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

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

相关文章

以命令行形式执行Postman脚本(使用Newman)

目录 一、背景 二、Newman的安装 三、脚本准备 四、Newman的执行 五、生成报告 一、背景 ​ Postman的操作离不开客户端。但是在一些情况下可能无法使用客户端去进行脚本执行。比如在服务端进行接口测试。由此我们引入了Newman。Newman基于Node.js开发&#xff0c;它使您可…

【MySQL】表的查询与连接

文章目录 预备工作一、表的基本查询1、简单基本查询2、分组聚合统计3、基本查询练习 二、表的复合查询1、多表查询2、子查询2.1 **单行子查询**2.2 **多行子查询**2.3 **多列子查询**2.4 在from子句中使用子查询 3、合并查询 三、表的连接1、自连接2、内连接3、外连接 预备工作…

【C++】关键字 命名空间 输入输出 缺省函数

一&#xff0c;C关键字 C 总计 63 个关键字&#xff0c;C语言 32 个关键字 直接上图&#xff1a; asmdoifreturntrycontinueautodoubleinlineshorttypedefforbooldynamic_castintsignedtypeidpublicbreakelselongsizeoftypenamethrowcaseenummutablestaticunionwchar_tcatche…

Win11更新后瘦身C盘的两个小技巧

每当windows更新完后&#xff0c;就很容易出现一个现象&#xff0c;那便是C盘存储变红了。 这个时候&#xff0c;就会有方法指出&#xff1a;把C盘中的系统更新文件清理掉吧&#xff0c;这样C盘就又能瘦回去了&#xff01; 然而&#xff0c;当你兴冲冲地按照网上的教程点击C…

OPTEE之KASAN地址消毒动态代码分析

安全之安全(security)博客目录导读 目录 一、KASAN简介 二、OPTEE_OS中KASAN配置选项 三、OPTEE_OS中KASAN配置选项打开 一、KASAN简介 内核地址消毒器(KASAN)是Linux内核的快速内存损坏检测器&#xff0c;KASAN检测slab、page_alloc、vmalloc、stack和全局内存中的越界、u…

Yarn基础入门

文章目录 一、Yarn资源调度器1、架构2、Yarn工作机制3、HDFS、YARN、MR关系4、作业提交之HDFS&MapReduce 二、Yarn调度器和调度算法1、先进先出调度器&#xff08;FIFO&#xff09;2、容量调度器&#xff08;Capacity Scheduler&#xff09;3、公平调度器&#xff08;Fair …

信钰证券:首板第二天买入技巧?

股票上市第一天&#xff0c;也就是所谓的“首板”&#xff0c;一般会引起商场的高度注重。那么关于投资者而言&#xff0c;如安在接下来的第二天进行买入是个十分要害的决议计划。本文将从多个角度剖析首板第二天买入技巧&#xff0c;供读者参阅。 首先&#xff0c;多数人或许…

京东数据平台:2023年服饰行业销售数据分析

最近看到有些消费机构分析&#xff0c;不少知名的运动品牌都把“主战场”放到了冲锋衣&#xff0c;那么羽绒服市场就比较危险了。但其实羽绒服市场也有机会点可寻。 先来说冲锋衣。的确&#xff0c;从今年的销售数据以及增长情况&#xff0c;冲锋衣的确会是今年冬天的大热门品…

领跑新周期!高通8295/8255上车,这家厂商已经整装待发

围绕高通下一代8295/8255平台&#xff0c;一场激烈的市场争夺战已经打响。 目前&#xff0c;各大域控制器厂商、汽车软件厂商围绕高通8295/8255平台&#xff0c;已经推出了诸多的解决方案&#xff0c;以抢占下一代高性能SoC带来的全新市场红利。《高工智能汽车》了解到&#x…

炮轰特斯拉「无图」,一家老牌图商的反击

‍作者|张祥威 编辑|德新 为实现城市NOA&#xff0c;车厂近年将重感知、轻地图奉为圭臬&#xff0c;一些玩家甚至提出「无图」概念&#xff0c;这让高精度地图供应商倍感压力。 四维图新CEO程鹏是坚定的反「无图」论者&#xff0c;他不太理解喊无图的目的是什么。后来和各家交…

idea 相关配置

idea 相关配置 / 设置 1. 插件设置 下载插件代理&#xff1a; https://plugins.jetbrains.com推荐插件下载&#xff1a; Sequence Diagram 时序图Rainbow Brackets 彩虹括号MyBatisX 在 MyBatis 中写的 mapper 可以跳转maven helper 右键运行 maven 指令Lombok简化 getter …

【数据库——MySQL(实战项目1)】(4)图书借阅系统——触发器

目录 1. 简述2. 功能代码2.1 创建两个触发器&#xff0c;分别在借出或归还图书时&#xff0c;修改借阅人表中的已借数目(附加&#xff1a;借阅人表的总借书数、图书表的借阅次数以及更新图书表的图书状态为(已借出/在架上))字段&#xff1b;2.2 创建触发器&#xff0c;当借阅者…

测试老鸟,Jmeter两种方法造接口性能测试数据(超细整理)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 通过接口构造测试…

mysql面试题51:你是如何监控你们的数据库的?你们的慢日志都是怎么查询的?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:你是如何监控你们的数据库的? 监控数据库是确保数据库系统稳定性和性能的重要工作。下面是一些常见的方法和工具: 监控工具选择:选择适合你的数…

排序:为什么插入排序比冒泡排序更受欢迎?

文章来源于极客时间前google工程师−王争专栏。 需掌握的的排序&#xff1a;冒泡排序、插入排序、选择排序、归并排序、快速排序、计数排序、基数排序、桶排序。按照时间复杂度可以分为三类&#xff1a; 问题&#xff1a;插入排序和冒泡排序的时间复杂度相同&#xff0c;都是O(…

UML组件图综合指南:设计清晰、可维护的软件系统

介绍&#xff1a; UML&#xff08;Unified Modeling Language&#xff09;组件图是软件系统设计中的重要工具&#xff0c;用于描绘系统的物理结构和组件之间的关系。在软件工程中&#xff0c;通过创建清晰的组件图&#xff0c;团队能够更好地理解系统的模块化结构和组织关系&a…

PTE考试解析

Pte 考试题目 注入漏洞 空格被过滤 用/**/代替空格&#xff0c;发现#被过滤 对&#xff03;进行url编码为%23 输入构造好的payload http://172.16.12.100:81/vulnerabilities/fu1.php?id1%27)/**/and/**/11%23 http://172.16.12.100:81/vulnerabilities/fu1.php?id1%27)/*…

uniapp 一次性上传多条视频 u-upload accept=“video“ uni.chooseMedia uni.uploadFile

方式 一 部分安卓机 只能一条一条传视频 文档地址 uview 2.0 Upload 上传组件 html <view class"formupload"><u-upload accept"video":fileList"fileList3" afterRead"afterRead" delete"deletePic" name"…

解锁远程联机模式:使用MCSM面板搭建我的世界服务器,并实现内网穿透公网访问

文章目录 前言1.Mcsmanager安装2.创建Minecraft服务器3.本地测试联机4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射内网端口 5.远程联机测试6. 配置固定远程联机端口地址6.1 保留一个固定TCP地址6.2 配置固定TCP地址 7. 使用固定公网地址远程联机 前言 MCSManager是一个…

动态资源平衡:主流虚拟化 DRS 机制分析与 SmartX 超融合的实现优化

资源的动态调度是虚拟化软件&#xff08;或超融合软件&#xff09;中的一项重要功能&#xff0c;主要指在虚拟化集群中&#xff0c;通过动态改变虚拟机的分布&#xff0c;达到优化集群可用性的目标。这一功能以 VMware vSphere 发布的 Distributed Resource Scheduler&#xff…