C++单例模式终极指南,深度解析不同应用场景,学单例这一篇就够了

news2024/10/7 10:22:10

📋 前言

  • 🖱 博客主页:在下马农的碎碎念
  • 🤗 欢迎关注🔎点赞👍收藏⭐️留言📝
  • ✍ 本文由在下马农原创,首发于CSDN
  • 📆 首发时间:2023/8/25
  • 📅 最近更新时间:2023/08/28
  • 🤵 此马非凡马,房星本是星。向前敲瘦骨,犹自带铜声。
  • 📇 系列文章目录: 暂无
  • 🙏作者水平有限,如发现错误,请留言轰炸哦!万分感谢!

须知少年凌云志,曾许人间第一流
——清·吴庆坻《题三十小像》

在这里插入图片描述
以下是正文

一、什么是单例模式?

  单例模式是一种常用的设计模式,它保证一个类只有一个实例,并且提供了全局访问该实例的方法。在C++中,单例模式的实现方式有多种。在单例模式中,通常使用一个静态方法或者一个静态变量来保存实例。这个静态方法或者静态变量可以被所有需要访问该实例的对象共享,并且在第一次调用时创建实例。之后每次调用该方法或者访问该变量时,都返回同一个实例。


单例模式的特点:

  • 一个类只有一个实例
  • 该实例在程序运行的整个周期内始终存在
  • 该实例可以被全局访问

  单例模式可以用于控制资源的访问,例如数据库连接池、线程池等。它还可以用来确保系统中某些组件只有一个实例,例如配置文件管理器、日志记录器等。

二、单例模式的实现方式

2.1 饿汉式:

特点:实现简单,线程安全,但可能造成内存浪费
适用情况: 单例对象在程序运行过程中频繁被调用

  饿汉式是最简单的一种单例模式实现方式,它在程序启动时就创建了单例对象,因此也被称为“急切创建”方式。这种方式的优点是实现简单且线程安全,因为这种方式在单例对象被使用之前就已经创建好了,因此不存在多线程环境下的竞争问题,但是缺点是如果该对象很少被使用,会造成内存浪费。
  饿汉式单例模式的实现方式一般是将单例模式定义为静态成员变量,并在类定义中就初始化它,这样单例对象就会在类装载的时候进行创建,在整个程序结束时按序销毁。
具体实现如下:

class Singleton {
public:
    static Singleton* getInstance();
private:
    Singleton();
    ~Singleton();
    Singleton(const Singleton &signal);
    const Singleton &operator=(const Singleton &signal);
private:
	// 唯一的单例对象
    static Singleton *instance_;
};
// 代码一运行就初始化创建实例 ,本身就线程安全
Singleton* Singleton::instance_= new (std::nothrow) Singleton();
Singleton* Singleton::getInstance() {
    return instance_;
}
Singleton::Singleton() {}
Singleton::~Singleton() {}

在上面的示例代码中,Singleton类的instance_成员变量是一个静态成员变量,它被定义为私有的,只能通过getInstance()方法来访问。getInstance()方法返回的是instance_的引用,通过这种方式来保证只有一个实例被创建。由于instance_被定义为静态成员变量,它会在程序启动时就被初始化。由于该方式在程序启动时就创建了单例对象,因此被称为“饿汉式单例模式”。

2.2 懒汉式

特点:延迟创建对象实例,避免了不必要的资源消耗,但是在多线程环境中线程不安全,需要加锁保证线程安全
适用情况: 单例对象的创建和初始化过程比较耗时,而且在程序运行过程中可能并不总是需要使用该对象,对资源敏感时也不叫使用,如果线程安全没什么要求,也可以用

懒汉式是另一种常见的单例模式实现方式,它在第一次访问单例对象时才进行创建。具体实现如下:

头文件Singleton.h:

class Singleton {
private:
    // 私有的构造函数,防止外部创建对象
    Singleton();
    // 单例对象的指针
    static Singleton* instance;
public:
    // 获取单例对象的静态方法
    static Singleton* getInstance();
};

源文件Singleton.cpp

#include "Singleton.h"
Singleton* Singleton::instance = nullptr;
Singleton::Singleton() {
    // 进行初始化操作
}
Singleton* Singleton::getInstance() {
    if (instance == nullptr) {
        instance = new Singleton();
    }
    return instance;
}

  上述代码中,Singleton类的构造函数是私有的,防止外部直接创建对象。instance指针被初始化为nullptr。在getInstance()方法中,首先检查instance是否为nullptr,如果是,则说明还没有创建单例对象,需要进行创建。创建成功后,将其赋值给instance指针,并返回该指针。`

  注意懒汉式单例模式的特点是在第一次请求时才创建对象,避免了程序启动时的资源浪费,但需要注意在多线程环境下的线程安全性以上示例是简单的单线程示例,在多线程环境下需要添加线程安全的措施,比如使用互斥锁或双重检查锁定等机制来保证线程安全性。

2.3 双重检查锁

C++中的双检锁(Double-Checked Locking)实现单例模式是一种在多线程环境下延迟创建单例对象的方式,通过使用双重检查来提高性能。

头文件:

class Singleton {
public:
    // 获取单例实例的静态方法
    static Singleton* getInstance();
    // 删除拷贝构造函数和赋值运算符重载,确保单例的唯一性
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
private:
    // 私有构造函数,防止外部实例化
    Singleton();

    static Singleton* instance; // 单例实例指针
    static std::mutex mutex;    // 互斥锁
};

源文件:

#include "Singleton.h"

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;

Singleton::Singleton() {
    // 构造函数
}
Singleton* Singleton::getInstance() {
    if (instance == nullptr) {
        std::lock_guard<std::mutex> lock(mutex); // 加锁

        if (instance == nullptr) {
            instance = new Singleton();
        }
    }
    return instance;
}

注意: 双检锁(Double-Checked Locking)在某些情况下可能不是线程安全的,尤其是在特定的编译器和硬件平台上。这是由于编译器和处理器的优化行为可能导致双检锁失效,从而导致多个线程同时创建实例。

具体来说,双检锁的问题源于指令重排序(instruction reordering)和多核处理器的内存可见性(memory visibility)。编译器和处理器为了提高执行效率,可能会对代码中的指令进行重排序,而不考虑程序员的意图。这种重排序可能会导致在检查 instance 是否为 nullptr 之后,但在实际创建实例之前,另一个线程就已经读取到了一个尚未完全初始化的实例。

2.4 静态局部变量(推荐!!!实现简单,轻松易学)

使用静态局部变量的方式实现单例模式是一种简洁且线程安全的方法。无需显式使用互斥锁或原子操作,能够在需要时按需创建单例实例,并且在整个程序生命周期内保持单例的唯一性。

头文件:

class Singleton {
public:
    static Singleton& getInstance();

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton();
};

源文件:

#include "Singleton.h"
Singleton& Singleton::getInstance() {
    static Singleton instance;
    return instance;
}
Singleton::Singleton() {
    // 构造函数
}

在上述代码中,getInstance() 方法返回一个对静态局部变量 instance 的引用。静态局部变量在函数首次调用时被初始化,并且在整个程序生命周期内保持存在。由于静态局部变量在 C++ 中具有线程安全的保证,因此无需显式使用互斥锁或原子操作来保护实例的创建过程。

总结

以上就是C++单例模式的所有实现方式。不同的实现方式在性能、线程安全等方面有所区别,具体实现方式应该根据实际情况进行选择,同时,需要注意在多线程环境下进行线程安全的处理,推荐优先使用静态局部变量方式。

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

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

相关文章

引入嵌入和向量搜索时的三个错误

将非结构化数据表示为嵌入向量以及使用向量搜索进行基于嵌入的检索 (embedding-based retrieval - EBR) 比以往任何时候都更加流行。 嵌入到底是什么&#xff1f; Roy Keyes 在《嵌入的最短定义&#xff1f;》中对此进行了很好的解释。 嵌入是学习的转换&#xff0c;使数据更有…

C语言环境搭建(Win)

一、C语言简介 1、 C语言简介 C语言是一门通用的、面向过程式的编译型语言&#xff0c;它的运行速度极快&#xff0c;仅次于汇编语言。 C语言是计算机产业的核心程序设计语言&#xff0c;操作系统、硬件驱动、关键组件、数据库等都离不开C语言&#xff0c;广泛应用于底层开发。…

10CQRS

本系列包含以下文章&#xff1a; DDD入门DDD概念大白话战略设计代码工程结构请求处理流程聚合根与资源库实体与值对象应用服务与领域服务领域事件CQRS&#xff08;本文&#xff09; 案例项目介绍 # 既然DDD是“领域”驱动&#xff0c;那么我们便不能抛开业务而只讲技术&…

SigFit—光-机-热耦合分析工具

美国Sigmadyne公司的SigFit软件是光机热耦合分析工具&#xff0c;可以将有限元分析得到的光学表面变形等结果文件通过多项式拟合或插值转化为光学分析软件的输入文件&#xff0c;还可实现动态响应分析、光程差分析、设计优化、主动控制/自适应控制光学系统的促动器布局及优化等…

美篇作文网教学资源源码-自带作文数据

非常漂亮的UI设计和页面排版&#xff01; 自适应手机pc端 页面内容均支持自定义 可以用来做网站矩阵&#xff0c;或者增强你其他网站板块&#xff0c;或者单独运营都可以。 可以通过广告方式变现&#xff0c;或者引流等等 友好的seo&#xff0c;更容易被浏览器收录 关注青狐…

软件测试面试题 —— 整理与解析(5)

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;【Austin_zhai】 &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xff0c;分享行业相关最新信息。…

5153. 删除

题目&#xff1a; 样例1&#xff1a; 输入 3454 输出 YES 344 样例2&#xff1a; 输入 10 输出 YES 0 样例3&#xff1a; 输入 111111 输出 NO 思路&#xff1a; 这道题就三个条件 关键条件是 能够被 8 整除。 而能被 8 整除的有一个重要的性质是 能够被 8 整除的重要…

MySQL - 全表分组后,获取组内排序首条数据信息

性能 不详!!! 不详!!! 不详!!! 请谨慎使用!!!环境 MySQL服务: 8.0版本;思路 使用8.0版本的新函数特性: row_number(): 序号函数; 顾名思义, 就是给每组中的元素从1开始按顺序加上序号;over(): 其中两个语法如下 partition: 按某字段分组;order by: 按某字段排序;注意: 两函数详…

【深度学习实验】前馈神经网络(final):自定义鸢尾花分类前馈神经网络模型并进行训练及评价

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入必要的工具包 1. 构建数据集&#xff08;IrisDataset&#xff09; 2. 构建模型&#xff08;FeedForward&#xff09; a. __init__(初始化) b. forward(前向传播) 3.整合训练、评估…

Json文件反序列化读取

Json文件 [{"name":"清华大学","location":"北京","grade":"1"},{"name":"北京大学","location":"北京","grade":"2"} ] 安装包 代码 Program.c…

Redis应用(8)——Redis的项目应用:结合SpringBoot如何在Redis里面存对象 RedisUtil工具类的封装 遇到的问题

前言 Redis作为一款优秀的开源、高效的内存数据库&#xff0c;在各种项目中都能见到其身影&#xff0c;熟练使用Redis是程序员必备的技能之一。本系列博客结合应用场景&#xff0c;阐述Redis从安装到使用的&#xff0c;从入门到进阶的相关内容。 本篇博客介绍在Spring项目中&…

免费AI人工智能,人工智能写文章软件

在当今信息爆炸的时代&#xff0c;内容创作已经成为了无处不在的需求。从博客、新闻报道到广告宣传&#xff0c;人们对于高质量的文本内容的需求愈发迫切。然而&#xff0c;面对繁忙的生活节奏和不断增长的写作任务&#xff0c;许多创作者感到焦头烂额。 从博客作者到广告写手&…

系统集成|第十六章(笔记)

目录 第十六章 信息&#xff08;文档&#xff09;和配置管理16.1 文档管理16.2 配置管理 上篇&#xff1a;第十五章、采购管理 下篇&#xff1a;第十七章、变更管理 第十六章 信息&#xff08;文档&#xff09;和配置管理 16.1 文档管理 信息系统项目相关信息&#xff08;文档…

记录一次错误---想让U-net网络输入大小不一致的图片

最近在看Deeplab系列的论文&#xff0c;文中提到了语义分割领域的一个难题是&#xff1a;将图片输入网络之前需要resize成统一大小&#xff0c;但是resize的话会造成细节信息的损失&#xff0c;所以想要网络处理任意大小的图片输入。我之前训练的U-net网络都是resize成224*224大…

基于若依ruoyi-nbcio支持flowable流程角色,同时修改流转用户为username,流程启动做大调整(三)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; ruoyi-nbcio: nbcio-boot的若依版本,基于ruoyi-flowable-plus和flowable6.7.2&#xff0c;目前处于移植功能阶段&#xff0c;目标是打造一个最好的若依平台上flowable流程管理系统开源版本&#xff0c…

【项目】Http服务器

【项目】Http服务器 项目简介 背景&#xff1a; http协议被广泛使用&#xff0c;从移动端&#xff0c;pc端浏览器&#xff0c;http协议无疑是打开互联网应用窗口的重要协议&#xff0c;http在网络应用层中的地位不可撼动&#xff0c;是能准确区分前后台的重要协议。 描述&a…

MD5 绕过第一式:弱比较绕过

文章目录 参考环境MD5韧性脆弱性md5() 隐式类型转换字符串连接数学运算布尔判断相等运算符 科学计数法科学计数法前缀 0E 与 0e PHP8 与 PHP 其他版本下字符串转化为数值的具体规则PHP8数值字符串优化 其他版本更为详细的讲解 字符串与字符串的弱比较字符串与数值的弱比较0e215…

git查看自己所在的分支

很多时候可能大家不太想切换其他工具&#xff0c;又不知道自己是否在自己需要操作的分支 可以直接终端执行 git branch此时 他就会在终端将所有的本地分支输出出来 并特殊标注自己所在的分支 这样我们就可以进一步去做自己想要做的操作了 当然 随着各种编辑器的发展 这个命令…

视频剪辑软件哪个好? 2023年最新功能解析

如今聊到视频的话题&#xff0c;大家可能都注意到近年来火爆的抖音短视频。从图片到动画&#xff0c;从动画到视频。时代在发展&#xff0c;技术在更新。到了近几年小视频也火了&#xff0c;其实不乏缺有视频剪辑软件的功劳。现在不少朋友在业余时间都喜欢剪辑视频来丰富自己的…

支撑电动汽车规模化,特来电智能化升级群充产品

9月26日&#xff0c;中国领先的充电网生态运营商特来电重磅发布智能群充4.0产品&#xff0c;标志着特来电群充产品体系进一步升级&#xff0c;充电行业迎来更高质量、更高性能的设备与系统&#xff0c;充电网基础设施将更好地支撑大规模电动汽车的发展。 群充技术路线引领充电…