C++单例模式精解

单例模式(重点*)

单例模式是23种常用设计模式中最简单的设计模式之一,它提供了一种创建对象的方式,确保只有单个对象被创建。这个设计模式主要目的是想在整个系统中只能出现类的一个实例,即一个类只有一个对象。

将单例对象创建在静态区

根据已经学过的知识进行分析:

  1. 将构造函数私有;

  2. 通过静态成员函数getInstance创建局部静态对象,确保对象的生命周期和唯一性;

  3. getInstance的返回值设为引用,避免复制;

    image-20240308174000886

    image-20240308174029507

隐患:如果单例对象所占空间较大,可能会对静态区造成内存压力。

class Point
{
public://定义为静态函数是因为要创建对象而调用成员函数又需要对象来调用,所以就将成员函数定义为静态成员函数直接使用类来进项调用// 使用& 是因为防止返回发生拷贝构造static Point & getInstance(){static Point pt(1,2);return pt;}void print() const{cout << "(" << this->_ix<< "," << this->_iy<< ")" << endl;}private:Point(int x,int y): _ix(x), _iy(y){cout << "Point(int,int)" << endl;}
private:int _ix;int _iy;
};void test0(){//使用&来接收就不会发生拷贝构造//这是pt和pt2指向就是相同的Point & pt = Point::getInstance();pt.print();Point & pt2 = Point::getInstance();pt2.print();cout << &pt << endl;cout << &pt2 << endl;
}

将单例对象创建在堆区

既然将单例对象创建在全局/静态区可能会有内存压力,那么为这个单例对象动态分配空间是比较合理的选择。请尝试实现代码:

分析:
  1. 构造函数私有;

  2. 因为非静态对象没有唯一性,所以我们要人为加一个判断语句来看是否第一次调用getInstance函数,所以在类中声明一个静态成员(因为我们想用它在静态成员函数中做判断)来接收通过静态成员函数getInstance创建堆上的对象,返回Point*类型的指针,如果该静态成员_pInstance为空就可以创建对象;

  3. 通过静态成员函数完成堆对象的回收。

    image-20240308181905940

    image-20240308181713168

                多个指针指向同一块空间,是比较危险的,如果我像上面代码那样将代码进行回收了,我在用其他指向原来那块空间的指针来访问就会出问题,所以为了避免问题,就使用下面的单例模式的规范。

image-20240308181807966

可能会对析构函数产生误解,析构函数是用来回收数据成员申请的堆空间的,而上面的数据成员并没有申请堆空间。

假如是将代码加到析构函数中使用析构函数来回收空间会怎么样呢?

调用析构函数,进入if判断不为nullptr,调用delete,而delete的第一步又是调用析构函数,这样就进去无限的循环直到栈的空间被占满。

这里不使用注释那样来调用destory是因为destory一开始设计不是static,需要对象来调用,而且这也还会再一次调用创建对象的成员函数,所以直接将destory设计为static,直接使用类名来调用。

上面定义的是一个死的数据因为是单例模式只能让他进行一次初始化,我们如果想要使单例的数据可以修改呢?

我们可以将上面的getInstance的构造函数改为无参构造只进行空间的申请,不对空间的数据进行初始化,然后通过这个函数的返回值再次调用初始化数据函数(init),使数据可以进行修改。

image-20240309100738528

单例对象的数据成员申请堆空间

要求:实现一个单例的Computer类,包含品牌和价格信息。
#include <string.h>
#include <iostream>
using std ::cout;
using std ::endl;
class Computer
{
public:static Computer *getInstance(){if (_pInstance == nullptr)_pInstance = new Computer();return _pInstance;}static void destroy(){if (_pInstance){delete _pInstance;_pInstance = nullptr;}cout << "heap delete" << endl;}void init(const char *brand, double price){if (_brand){delete[] _brand;_brand = nullptr;}_brand = new char[strlen(brand) + 1]();strcpy(_brand, brand);_price = price;}void print(){cout << _brand << endl;cout << _price << endl;}private:// 构造函数Computer() {};Computer(const char *brand, double price): _brand(new char[strlen(brand) + 1]()), _price(price){strcpy(_brand, brand);}// 析构函数~Computer(){if (_brand){delete _brand;_brand = nullptr;}cout << "~Computer" << endl;}Computer(const Computer &rhs) = delete;Computer &operator=(const Computer &rhs) = delete;char *_brand;double _price;static Computer *_pInstance;
};
Computer *Computer ::_pInstance = nullptr;int main()
{Computer ::getInstance()->init("bob", 2222);Computer ::getInstance()->print();Computer ::getInstance()->init("tom", 6666);Computer ::getInstance()->print();Computer ::destroy();return 0;
}

image-20240309102850728

image-20240309102833694

单例模式的应用场景

1、有频繁实例化然后销毁的情况,也就是频繁的 new 对象,可以考虑单例模式;

2、创建对象时耗时过多或者耗资源过多,但又经常用到的对象;

3、当某个资源需要在整个程序中只有一个实例时,可以使用单例模式进行管理(全局资源管理)。例如数据库连接池、日志记录器等;

4、当需要读取和管理程序配置文件时,可以使用单例模式确保只有一个实例来管理配置文件的读取和写入操作(配置文件管理);

5、在多线程编程中,线程池是一种常见的设计模式。使用单例模式可以确保只有一个线程池实例,方便管理和控制线程的创建和销毁;

6、GUI应用程序中的全局状态管理:在GUI应用程序中,可能需要管理一些全局状态,例如用户信息、应用程序配置等。使用单例模式可以确保全局状态的唯一性和一致性。

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

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

相关文章

【微服务】java中http调用组件深入实战详解

目录 一、前言 二、http调用概述 2.1 什么是http调用 2.1.1 http调用步骤 2.2 HTTP调用特点 2.3 HTTP调用应用场景 三、微服务场景下http调用概述 3.1 微服务开发中http调用场景 3.2 微服务组件中http的应用 四、常用的http调用组件 4.1 java中常用的http组件介绍 4…

Implementing SAP BPC Embedded - 2nd Edition

Implementing SAP BPC Embedded - 2nd Edition

stm32第四天控制蜂鸣器

一&#xff1a; 1.蜂鸣器的种类 蜂鸣器是一种常用的电子发声元器件&#xff0c;采用直流电压供电。广泛应用于计算机&#xff0c;打ED机&#xff0c;报警器&#xff0c;电子玩具&#xff0c;汽车电子设备灯等产品中常见的蜂鸣器可分为有源蜂鸣器和无源蜂鸣器。 2.蜂鸣器的控制…

Swift 中 associatedtype 的用法详解

目录 前言 1.什么是associatedtype 2.associatedtype 的作用 1.让协议支持泛型 2.让协议支持不同的数据类型 3.结合 where 关键字限制类型 4.什么时候使用 associatedtype 5.总结 前言 在 Swift 语言中&#xff0c;泛型&#xff08;Generics&#xff09;是一个非常强大…

每日Attention学习26——Dynamic Weighted Feature Fusion

模块出处 [ACM MM 23] [link] [code] Efficient Parallel Multi-Scale Detail and Semantic Encoding Network for Lightweight Semantic Segmentation 模块名称 Dynamic Weighted Feature Fusion (DWFF) 模块作用 双级特征融合 模块结构 模块思想 我们提出了 DWFF 策略&am…

OpenCV实现图像特征提取与匹配

‌一、特征检测与描述子提取‌ ‌选择特征检测器‌ 常用算法包括&#xff1a; ‌ORB‌&#xff1a;一种高效的替代SIFT和SURF的算法&#xff0c;主要用于移动机器人和增强现实等领域。适合实时应用&#xff0c;结合FAST关键点与BRIEF描述子‌。‌SIFT&#xff08;尺度不变特征变…

向量检索在AI中的应用与技术解析

关键要点 向量检索在AI中用于信息检索、推荐系统和图像搜索&#xff0c;研究表明其通过高维空间中的向量表示数据来提升搜索相关性。它依赖于嵌入技术&#xff08;如Word2Vec、BERT&#xff09;和近邻算法&#xff08;如kNN、ANN&#xff09;&#xff0c;证据倾向于其在处理大…

事务与异步方法(@Async)协同工作

目录 1. 问题场景与风险 &#xff08;1&#xff09;典型场景 &#xff08;2&#xff09;风险分析 2. 解决方案&#xff1a;事务提交后触发异步操作 &#xff08;1&#xff09;代码示例 &#xff08;2&#xff09;关键注解 3. 原理解析 &#xff08;1&#xff09;事务同…

关于进程的实验(子进程和父进程相关的)

文章目录 1.第一个问题2.第二个问题3.第三个问题 1.第一个问题 编写一段程序&#xff0c;利用系统调用fork( )创建两个进程。当此程序运行时&#xff0c;在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符&#xff1a;父进程显示字符“a”;子进程分别显…

MyBatis 如何创建 SqlSession 对象的?

MyBatis 创建 SqlSession 对象的过程主要由 SqlSessionFactory 接口及其实现类来完成。以下是详细步骤&#xff1a; 1. SqlSessionFactory 接口: SqlSessionFactory 是 MyBatis 的核心接口之一&#xff0c;它负责创建 SqlSession 对象。 你可以将 SqlSessionFactory 视为 Sql…

深度优先搜索(DFS)剪枝技术详解与C++实现

深度优先搜索&#xff08;DFS&#xff09;剪枝技术通过提前终止无效路径的搜索&#xff0c;大幅提升算法效率。以下是五种核心剪枝技术的详细解析及C代码示例&#xff1a; 目录 一、可行性剪枝 C实现示例 二、搜索顺序剪枝 伪代码逻辑 三、最优性剪枝 C实现示例 四、排除…

【双指针】移动零

题目描述&#xff1a; 算法分析&#xff1a; 观察输入输出&#xff1a; 输出中一共分为两个区域&#xff0c;0区和非零区。 但是在处理未完成之前&#xff0c;必然存在着一个零和非零数共存的区域&#xff0c;所以在处理的过程当中一共有三个区域&#xff0c;0区&#xff0c;…

学习15天:pytest

1、.pytest强大的插件 pytest-html(生成html格式的自动化测试报告) pytest-xdist测试用例分布式执行。多CPU分发。 pytest-ordering 用于改变测试用例的执行顺序 pytest-rerunfailures用例失败后重跑 allure-pytest 用于生成美观的测试报告。 2、规则&#xff1a; 模块…

股票交易所官方api接口有哪些?获取和使用需要满足什么条件

炒股自动化&#xff1a;申请官方API接口&#xff0c;散户也可以 python炒股自动化&#xff08;0&#xff09;&#xff0c;申请券商API接口 python炒股自动化&#xff08;1&#xff09;&#xff0c;量化交易接口区别 Python炒股自动化&#xff08;2&#xff09;&#xff1a;获取…

2.7 滑动窗口专题:串联所有单词的子串

LeetCode 30. 串联所有单词的子串算法对比分析 1. 题目链接 LeetCode 30. 串联所有单词的子串 2. 题目描述 给定一个字符串 s 和一个字符串数组 words&#xff0c;words 中所有单词长度相同。要求找到 s 中所有起始索引&#xff0c;使得从该位置开始的连续子串包含 words 中所…

【区块链】区块链密码学基础

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 区块链密码学基础引言一、哈希函数1.1 基本概念1.2 数学表达 二、非对称加密2.1…

Spring Boot配置类原理、Spring Boot核心机制理解,以及实现自动装置的底层原理

目的:从底层源码角度分析 Spring Boot 配置类以及自动装载的底层原理 文章目录 1. Spring Boot 配置类实现自动装载1.1 @Configuration注解1.2 @Configuration 注解完成 bean 注入流程图1.3 @ConfigurationProperties注解赋值2. Spring Boot的核心机制:自动装配2.1 @SpringBo…

docker桌面版启动redis,解决无法连接

docker run -d --name redis -p 6379:6379 -v E:\2\redis\redis.conf:/usr/local/etc/redis/redis.conf redis redis-server /usr/local/etc/redis/redis.conf 在本地创建一个目录&#xff0c;里面有个redis.conf文件&#xff0c;内容如下&#xff0c;启动时绑定这个配置文件目…

[网络][tcp协议]:tcp报头

tcp(传输控制协议)是一种面向字节流的传输层协议,相较于udp协议,tcp能保证传输数据的可靠性与准确性,tcp也是目前最常见的传输层协议 本文主要介绍tcp报头各个字段的含义与用途 注:保留6位和6位标记位是目前最普遍的写法,在我查资料时,发现有一些拓展情况,会在后文细说 最简单的…

【虚幻C++笔记】引擎源码下载及编译步骤

目录 1.在GitHub上访问虚幻引擎源代码2.安装Visual Studio 20223.解压完成以后&#xff0c;打开源码的根目录&#xff0c;选择Setup.bat运行4.选择GenerateProjectFiles.bat运行,生成uE5.sln文件&#xff0c;点击这个文件打开项目5.设置编译的选项&#xff0c;选择DevelopmentE…