C++单例模式精解

ops/2025/3/17 12:40:42/

单例模式(重点*)

单例模式是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.ppmy.cn/ops/166493.html

相关文章

【Spring】SpringIOC详解,包括源码分析,xml以及注解开发

SpringIOC Spring简介 ​ Spring是一个开源框架&#xff0c;它由[Rod Johnson]创建。它是为了解决企业应用开发的复杂性而创建的。 ​ 目前是JavaEE开发的灵魂框架。他可以简化JavaEE开发&#xff0c;可以非常方便整合其他框架&#xff0c;无侵入的进行功能增强。 ​ Sprin…

如何优雅地将Collection转为Map?

将Collection转换为Map是常见的需求&#xff0c;尤其是在处理数据时需要快速查找或去重。以下是几种常见的方法&#xff0c;包括使用谷歌的Maps.uniqueIndex、Hutool的CollUtil.toMap和Java Stream API的Collectors.toMap三种方法。 谷歌的Maps.uniqueIndex /*** 使用com.goo…

OSPF | LSDB 链路状态数据库 / SPF 算法 / 实验

注&#xff1a;本文为 “OSPF | LSDB / SPF ” 相关文章合辑。 LSDB 和 SPF 算法 潇湘浪子的蹋马骨汤 发布 2019-02-15 23:58:46 1. 链路状态数据库 (LSDB) 链路状态协议除了执行洪泛扩散链路状态通告&#xff08;LSA&#xff09;以及发现邻居等任务外&#xff0c;其第三个任…

电子电气架构 --- 智能电动汽车的品牌竞争转变

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 人生是一场骗局&#xff0c;最大的任务根本不是什么买车买房&#xff0c;也不是及时行乐&#xff0c;这就…

.NET Framework华为云流水线发布

文章目录 前言一、新建代码检查二、新建编译构建三、新建部署三、新建流水线 前言 华为云流水线发布&#xff1a;自动检查代码&#xff0c;打包发布到服务器 一、新建代码检查 检查代码是否存在报错 设置规则集 二、新建编译构建 三、新建部署 模板选择空模板或者自己去创建…

Kubernetes的组成和架构

Kubernetes&#xff08;K8s&#xff09;是一个开源的容器编排平台&#xff0c;用于自动化部署、扩展和管理容器化应用程序。它由多个组件组成&#xff0c;这些组件可以分为两类&#xff1a;控制平面&#xff08;Control Plane&#xff09;组件和节点&#xff08;Node&#xff0…

Sublime Text 2.0.2 安装与汉化指南:从下载到中文包配置的完整教程

Sublime Text 是一款轻量级、高性能的代码编辑器&#xff0c;深受开发者喜爱。Sublime Text 2.0.2 是一个较旧的版本&#xff0c;但仍然可以满足基本的代码编辑需求。以下是关于 Sublime Text 2.0.2 的安装、中文包配置以及使用方法的详细指南。 1. 下载 Sublime Text 2.0.2 提…

使用OpenCV和MediaPipe库——抽烟检测(姿态监控)

目录 抽烟检测的运用 1. 安全监控 (1) 公共场所禁烟监管 (2) 工业安全 2. 智能城市与执法 (1) 城市违章吸烟检测 (2) 无人值守管理 3. 健康管理与医疗 (1) 吸烟习惯分析 (2) 远程监护 4. AI 监控与商业分析 (1) 保险行业 (2) 商场营销 5. 技术实现 (1) 计算机视…