C++单例模式精解

news2025/3/17 13:52:41

单例模式(重点*)

单例模式是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.coloradmin.cn/o/2316648.html

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

相关文章

【java】集合练习2

Student.java&#xff1a;保存学生类的定义。 public class Student {private String name;private int age;public Student(String name, int age) {this.name name;this.age age;}public String getName() { return name; }public int getAge() { return age; }Overridepu…

FineBI_实现求当日/月/年回款金额分析

需求&#xff1a;原始数据结构如下&#xff0c;需要在分组表中&#xff0c;实现各城市当日/月/年的合同金额分析 实现步骤&#xff1a; ①维度拖入城市 ②分别取当日/月/年合同金额 当日DEF(SUM_AGG(${ 地区数据分析1 _ 合同金额 }),[${ 地区数据分析1 _ 城市 }],[LEFT(${ 地…

【计算机网络】2物理层

物理层任务:实现相邻节点之间比特(或)的传输 1.通信基础 1.1.基本概念 1.1.1.信源,信宿,信道,数据,信号 数据通信系统主要划分为信源、信道、信宿三部分。 信源:产生和发送数据的源头。 信宿:接收数据的终点。 信道:信号的传输介质。 数据和信号都有模拟或数字…

解决PC串流至IPad Pro时由于分辨率不一致导致的黑边问题和鼠标滚轮反转问题

问题背景 今天在做 电脑串流ipad pro 的时候发现了2个问题&#xff1a; 1.ipadpro 接上鼠标后&#xff0c;滚轮上下反转&#xff0c;这个是苹果自己的模拟造成的问题&#xff0c;在设置里选择“触控板与鼠标”。 关闭“自然滚动”,就可以让鼠标滚轮正向滚动。 2. ipadpro 分…

LLMs之CoD:《Chain of Draft: Thinking Faster by Writing Less》翻译与解读

LLMs之CoD&#xff1a;《Chain of Draft: Thinking Faster by Writing Less》翻译与解读 导读&#xff1a;这篇论文的核心是提出了一种名为“Chain of Draft”&#xff08;CoD&#xff0c;草稿链&#xff09;的新型提示策略&#xff0c;用于改进大型语言模型&#xff08;LLMs&a…

0CTF 2016 piapiapia 1

#源码泄露 #代码审计 #反序列化字符逃逸 #strlen长度过滤数组绕过 www.zip 得到源码 看到这里有flag &#xff0c;猜测服务端docker的主机里&#xff0c;$flag变量应该存的就是我们要的flag。 于是&#xff0c;我们的目的就是读取config.php 利用思路 这里存在 任意文件读取…

python_巨潮年报pdf下载

目录 前置&#xff1a; 步骤&#xff1a; step one: pip安装必要包&#xff0c;获取年报url列表 step two: 将查看url列表转换为pdf url step three: 多进程下载pdf 前置&#xff1a; 1 了解一些股票的基本面需要看历年年报&#xff0c;在巨潮一个个下载比较费时间&…

为什么需要使用十堰高防服务器?

十堰高防服务器的核心价值与应用必要性 一、‌应对复杂攻击的防御能力‌ ‌T级DDoS攻击防护‌ 十堰高防服务器搭载 ‌T级清洗中心‌&#xff0c;支持智能流量调度与分层处理&#xff0c;可抵御 ‌800Gbps-1.2Tbps‌ 的大规模混合攻击&#xff08;如SYN Flood、UDP反射&#xff…

人工智能中的线性代数基础详解

‌ 线性代数是人工智能领域的重要数学基础之一,是人工智能技术的底层数学支柱,它为数据表示、模型构建和算法优化提供了核心工具。其核心概念与算法应用贯穿数据表示、模型训练及优化全过程。更多内容可看我文章:人工智能数学基础详解与拓展-CSDN博客 一、基本介绍 …

【毕业论文格式】word分页符后的标题段前间距消失

文章目录 【问题描述】 分页符之后的段落开头&#xff0c;明明设置了标题有段前段后间距&#xff0c;但是没有显示间距&#xff1a; 【解决办法】 选中标题&#xff0c;选择边框 3. 选择段前间距&#xff0c;1~31磅的一个数 结果

【蓝桥杯每日一题】3.16

&#x1f3dd;️专栏&#xff1a; 【蓝桥杯备篇】 &#x1f305;主页&#xff1a; f狐o狸x 目录 3.9 高精度算法 一、高精度加法 题目链接&#xff1a; 题目描述&#xff1a; 解题思路&#xff1a; 解题代码&#xff1a; 二、高精度减法 题目链接&#xff1a; 题目描述&…

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

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

电脑实用小工具--VMware常用功能简介

一、创建、编辑虚拟机 1.1 创建新的虚拟机 详见文章新创建虚拟机流程 1.2 编辑虚拟机 创建完成后&#xff0c;点击编辑虚拟机设置&#xff0c;可对虚拟机内存、处理器、硬盘等各再次进行编辑设置。 二、虚拟机开关机 2.1 打开虚拟机 虚拟机创建成功后&#xff0c;点击…

为训练大模型而努力-分享2W多张卡通头像的图片

最近我一直在研究AI大模型相关的内容&#xff0c;想着从现在开始慢慢收集各种各样的图片&#xff0c;万一以后需要训练大模型的时候可以用到&#xff0c;或者自己以后也许会需要。于是决定慢慢收集这些图片&#xff0c;为未来的学习和训练大模型做一些铺垫&#xff0c;哈哈。 …

JVM 垃圾回收器的选择

一&#xff1a;jvm性能指标吞吐量以及用户停顿时间解释。 二&#xff1a;垃圾回收器的选择。 三&#xff1a;垃圾回收器在jvm中的配置。 四&#xff1a;jvm中常用的gc算法。 一&#xff1a;jvm性能指标吞吐量以及用户停顿时间解释。 在 JVM 调优和垃圾回收器选择中&#xff0…

使用GPTQ量化Llama-3-8B大模型

使用GPTQ量化8B生成式语言模型 服务器配置&#xff1a;4*3090 描述&#xff1a;使用四张3090&#xff0c;分别进行单卡量化&#xff0c;多卡量化。并使用SGLang部署量化后的模型&#xff0c;使用GPTQ量化 原来的模型精度为FP16&#xff0c;量化为4bit 首先下载gptqmodel量化…

2025-03-16 学习记录--C/C++-PTA 习题4-2 求幂级数展开的部分和

合抱之木&#xff0c;生于毫末&#xff1b;九层之台&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、题目描述 ⭐️ 习题4-2 求幂级数展开的部分和 已知函数e^x可以展开为幂级数1xx^2/2!x^3/3!⋯x^k/k!⋯。现给定一个实数x&a…

【C#】Http请求设置接收不安全的证书

在进行HTTP请求时&#xff0c;出现以下报错&#xff0c;可设置接收不安全证书跳过证书验证&#xff0c;建议仅测试环境设置&#xff0c;生产环境可能会造成系统漏洞 /// <summary> /// HttpGet请求方法 /// </summary> /// <param name"requestUrl"&…

AP AR

混淆矩阵 真实值正例真实值负例预测值正例TPFP预测值负例FNTN &#xff08;根据阈值预测&#xff09; P精确度计算&#xff1a;TP/(TPFP) R召回率计算&#xff1a;TP/(TPFN) AP 综合考虑P R 根据不同的阈值计算出不同的PR组合&#xff0c; 画出PR曲线&#xff0c;计算曲线…

Leetcode-1278.Palindrome Partitioning III [C++][Java]

目录 一、题目描述 二、解题思路 【C】 【Java】 Leetcode-1278.Palindrome Partitioning IIIhttps://leetcode.com/problems/palindrome-partitioning-iii/description/1278. 分割回文串 III - 力扣&#xff08;LeetCode&#xff09;1278. 分割回文串 III - 给你一个由小写…