设计模式:单例模式(Singleton Pattern)

文章目录

    • 一、单例模式的概念
    • 二、单例模式的结构
    • 三、常见实现方式
      • 3.1 饿汉式单例
      • 3.2 懒汉式单例

一、单例模式的概念

单例模式(Singleton Pattern)是一种创建型设计模式,它的核心思想是:保证在一个进程中,某个类仅有一个实例,并提供全局访问点。

在这里插入图片描述
问题:
单例模式同时解决了两个问题, 所以违反了单一职责原则:
1.保证一个类只有一个实例。 为什么会有人想要控制一个类所拥有的实例数量? 最常见的 原因是控制某些共享资源 (例如数据库或文件) 的访问权限。

它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。

注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。

在这里插入图片描述

客户端甚至可能没有意识到它们一直都在使用同一个对象。

2.为该实例提供一个全局访问节点。 还记得你用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。

和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。

还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。

常见应用场景:

  • 日志系统:全局唯一的日志记录器。
  • 配置管理:全局读取配置文件。
  • 线程池、数据库连接池:全局资源管理。
  • 设备驱动对象:保证唯一控制入口。

单例模式的关键要素:

  • 构造函数私有化:防止外部随意 new。
  • 拷贝构造与赋值运算符删除:防止复制对象。
  • 静态成员指针/对象:存储唯一实例。
  • 公共静态方法:提供获取实例的入口。

二、单例模式的结构

所有单例的实现都包含以下两个相同的步骤:

  • 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。
  • 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。

在这里插入图片描述

三、常见实现方式

  1. 在类中添加一个私有静态成员变量用于保存单例实例。
  2. 声明一个公有静态构建方法用于获取单例实例。
  3. 在静态方法中实现"延迟初始化"。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例。
  4. 将类的构造函数设为私有。 类的静态方法仍能调用构造函数, 但是其他对象不能调用。
  5. 检查客户端代码, 将对单例的构造函数的调用替换为对其静态构建方法的调用。

函数内静态变量初始化的线程安全问题:
在 C++98/03 中:

  • 函数内的静态局部变量在第一次调用函数时初始化。
  • 多线程调用时,如果两个线程同时进入函数,就可能同时执行初始化,造成多次构造,属于竞态条件(Race Condition)。

所以在 C++98/03 里,需要手动加锁才能保证安全。

C++11 标准明确规定:函数内静态局部变量在第一次初始化时,初始化过程是线程安全的。
也就是说:

  • 即使多个线程同时调用 getInstance(),只有一个线程会执行构造函数。
  • 其他线程会等待构造完成,然后使用同一个实例。

3.1 饿汉式单例

饿汉式的特点:

  • 类加载时就创建实例,不管你用不用它,它都存在。
  • 线程安全:因为实例在程序开始时就初始化了,不存在多线程并发创建的问题。
  • 资源消耗:如果对象很大而一直没用到,会浪费内存。
  • 适用场景:对象创建成本低,且在程序运行中几乎一定会用到。

静态成员对象

#include <iostream>class Singleton {
private:Singleton() { std::cout << "Singleton Created\n"; } // 构造函数私有化~Singleton() { std::cout << "Singleton Destroyed\n"; }Singleton(const Singleton&) = delete;               // 禁止拷贝Singleton& operator=(const Singleton&) = delete;    // 禁止赋值static Singleton instance;   // 静态成员,类加载时即初始化public:static Singleton& getInstance() {return instance;         // 返回唯一实例}void show() {std::cout << "Hello Hungry Singleton!" << std::endl;}
};// 静态成员初始化(在程序启动时创建)
Singleton Singleton::instance;int main() {Singleton& s1 = Singleton::getInstance();Singleton& s2 = Singleton::getInstance();s1.show();std::cout << "s1 addr = " << &s1 << ", s2 addr = " << &s2 << std::endl;return 0;
}

输出结果:
在这里插入图片描述
实现原理(简要):

  • 编译器在生成代码时,会在静态变量前加上一次性标志(guard variable)。
  • 第一个线程进入函数时,检查标志:
    • 如果未初始化 → 执行构造函数 → 设置标志
    • 如果已初始化 → 直接返回实例
  • 编译器会保证对标志的写入和检查是原子操作或通过内部锁完成,从而保证线程安全。

3.2 懒汉式单例

懒汉式的特点:

  • 延迟初始化:只有第一次调用 getInstance() 时才创建对象。
  • 线程安全问题:多线程环境下,需要注意可能出现多个实例的问题。
  • 优点:节省资源,只有在真正需要时才创建。
  • 缺点:实现稍复杂,需要考虑多线程安全。

懒汉式实现(C++11 推荐写法)
C++11 后,使用 函数内静态变量 可以保证线程安全:

#include <iostream>class Singleton {
private:Singleton() { std::cout << "Singleton Created\n"; } // 构造私有~Singleton() { std::cout << "Singleton Destroyed\n"; }Singleton(const Singleton&) = delete;               // 禁止拷贝Singleton& operator=(const Singleton&) = delete;    // 禁止赋值public:static Singleton& getInstance() {static Singleton instance;  // 第一次调用时创建,C++11线程安全return instance;}void show() {std::cout << "Hello Lazy Singleton!" << std::endl;}
};int main() {Singleton& s1 = Singleton::getInstance();Singleton& s2 = Singleton::getInstance();s1.show();std::cout << "s1 addr = " << &s1 << ", s2 addr = " << &s2 << std::endl;return 0;
}

输出结果:
在这里插入图片描述
懒汉式的多线程安全写法(C++11 前)
如果在 C++11 之前,需要手动加锁防止多线程同时创建多个实例:

#include <mutex>class Singleton {
private:Singleton() {}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;static Singleton* instance;static std::mutex mtx;public:static Singleton* getInstance() {if (instance == nullptr) {std::lock_guard<std::mutex> lock(mtx);if (instance == nullptr) {instance = new Singleton();}}return instance;}
};// 静态成员初始化
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

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

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

相关文章

Swift 解法详解 LeetCode 362:敲击计数器,让数据统计更高效

文章目录 摘要 描述 题解答案 题解代码分析 代码讲解 示例测试及结果 时间复杂度 空间复杂度 总结 摘要 “敲击计数器”这道题听上去像个小游戏里的功能,但其实它背后对应的是一个常见的需求:在过去一段时间内统计事件发生的次数。比如网站的访问量统计、API 调用次数限制、…

coze工作流200+源码,涵盖AI文案生成、图像处理、视频生成、自动化脚本等多个领域

AI 博主风哥在github分享了 200 实用生产力coze工作流&#xff0c;涵盖AI文案生成、图像处理、视频生成、自动化脚本等多个领域&#xff0c;导入即用&#xff0c;项目地址https://github.com/Hammer1/cozeworkflows github下载慢也可前往该地址下载https://pan.baidu.com/s/1fC…

AI与SEO关键词协同优化

内容概要 人工智能&#xff08;AI&#xff09;技术的迅猛发展正深刻变革着搜索引擎优化&#xff08;SEO&#xff09;的实践方式&#xff0c;特别是在关键词策略这一核心领域。两者的深度融合&#xff0c;为企业在数字海洋中精准导航提供了前所未有的强大工具。通过AI驱动的智能…

【Unity开发】Unity核心学习(二)

二、动画基础 1、Animation动画窗口 &#xff08;1&#xff09;介绍&#xff08;2&#xff09;Animation窗口功能2、创建编辑动画 面板变化&#xff1a;动画文件界面&#xff1a;3、Animator动画状态机 &#xff08;1&#xff09;有限状态机概念&#xff08;2&#xff09;Anima…

NETSDK1045 当前 .NET SDK 不支持将 .NET 8.0 设置为目标。请将 .NET 5.0 或更低版本设置为目标,或使用支持

C# 项目中的目标框架无法修改并且显示为空 严重性 代码 说明 项目 文件 行 禁止显示状态 错误 NETSDK1045 当前 .NET SDK 不支持将 .NET 8.0 设置为目标。请将 .NET 5.0 或更低版本设置为目标&#xff0c;或使用支持 .NET 8.0 的 .NET SDK 版本。 Padim C:\Program …

MNIST 数据集mnist.npz详解

MNIST 数据集是机器学习领域最著名的数据集之一&#xff0c;全称为"Modified National Institute of Standards and Technology"数据库。它包含了大量手写数字的图像&#xff0c;是入门机器学习和深度学习的经典数据集。1. MNIST 数据集概述 60,000 张训练图像 10,00…

深入理解HTTPS:从概念到实战优化

深入理解HTTPS&#xff1a;从概念到实战优化一&#xff1a;概述二&#xff1a;工作流程三&#xff1a;创建自签名证书四&#xff1a;案例1&#xff09;案例一&#xff1a;HTTPS 搭建2&#xff09;案例二&#xff1a;HTTP/2 搭建3&#xff09;案例三&#xff1a;HTTP 重定向 HTT…

MySQL数据备份与恢复全攻略

一、数据备份与恢复按照备份方式分类&#xff1a;物理备份&#xff0c;直接复制数据库的物理文件&#xff0c;可以直接拷贝和恢复&#xff1b;逻辑备份&#xff0c;通过SQL语句导出数据库结构和数据&#xff0c;可用于不同版本和不同类型的MySQL数据库之间的数据迁移。按照数据…

单机多卡间大张量传输迷惑行为?

老铁们我最近真的好惨&#x1f62d;&#xff0c;一个大模型在单机多卡上运行就是出错&#xff0c;debug看的老眼昏花&#xff0c;最后发现大张量在设备间直接传输会有很发癫的行为&#xff0c;还请大家帮我看看&#x1f647;‍摒弃屎山一样的代码&#xff0c;简单运行下列脚本i…

无法将“pnpm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。

1 问题描述今天使用pnpm安装如下报错&#xff1a;pnpm : 无法将“pnpm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正确&#xff0c;然后再试一次。 所在位置 行:1 字符: 1pnpm install~~~~ Categor…

内核编译 day61

二&#xff1a;内核启动 一&#xff1a;流程 sudo vim /etc/network/interfaces sudo chmod 0666 /etc/default/tft...... //修改可读uboot命令&#xff1a; help/&#xff1f; &#xff1a; 帮助手册&#xff0c;列出uboot支持的所有命令 printenv/print 打印环境变量 sete…

【YOLOv5部署至RK3588】模型训练→转换RKNN→开发板部署

已在GitHub开源与本博客同步的YOLOv5_RK3588_object_detect项目&#xff0c;地址&#xff1a;https://github.com/A7bert777/YOLOv5_RK3588_object_detect/tree/main 详细使用教程&#xff0c;可参考README.md或参考本博客第六章 模型部署 文章目录一、项目回顾二、模型选择介绍…

Telematics Control Unit(TCU)的系统化梳理

1、Telematics Control Unit (TCU)概述 TCU中文名为远程信息处理控制单元&#xff0c;很多场合都称为Telematics Box&#xff0c;又叫TBox&#xff0c;顾名思义&#xff0c;一般都为一个独立的盒子&#xff08;如图2、图3所示&#xff09;&#xff0c;负责和云端的远程信息交互…

Appium学习笔记

adb构成client端&#xff0c;在电脑上&#xff0c;负责发送adb命令daemon守护进程&#xff0c;在手机上&#xff0c;负责接收和执行adb命令server端&#xff0c;在电脑上&#xff0c;负责管理client和daemon之间的通信![[Pasted image 20250825201322.png]]包名&#xff0c;对应…

栈指针(Stack Pointer)是什么?

栈指针(Stack Pointer)是什么? 首先,用一个简单易懂的方式解释栈指针(Stack Pointer)。 核心比喻:摞起来的书 想象有一摞书整齐地堆在桌面上: 这摞书就是“栈”(Stack),它是一种后进先出(LIFO) 的数据结构。你只能从最顶部拿走一本书(“弹出”),或者把一本新…

数据结构:红黑树(Red-Black Tree)

目录 从AVL树的“烦恼”说起 如何用“颜色”来定义“大致平衡”&#xff1f;—— 红黑树的五个规则 五个规则如何保证“大致平衡”&#xff1f; 用 C/C 代码定义红黑树的结构 定义颜色和节点结构 定义树的结构和哨兵节点 从AVL树的“烦恼”说起 我们从已经了解的 AVL 树出…

Ubuntu22.04安装VMware Tools

文章目录前言安装open-mv-tools前言 本教程使用的版本是Ubuntu22.04.5&#xff0c;由于虚拟机上面的重新安装VMware Tools是灰的&#xff0c;于是自动下载安装open-mv-tools&#xff0c; 安装open-mv-tools 打开终端&#xff0c;更新一下 sudo apt update这一步可能需要先…

DBeaver连接SQL Server时添加驱动后仍提示找不到驱动的解决方法

DBeaver连接SQL Server时添加驱动后仍提示找不到驱动的解决方法 在使用DBeaver连接SQL Server时&#xff0c;即使您已手动添加驱动文件&#xff0c;系统仍提示“找不到驱动”&#xff0c;这通常是由驱动配置错误、版本不兼容或SQL Server设置问题引起的。以下我将逐步为您提供解…

JVM之【类加载系统】

目录 前言 类加载过程 类加载 执行过程 加载阶段 连接阶段 初始化阶段 类加载器 BootstrapClassLoader ExtClassLoader AppClassLoader 类加载器之间的关系 双亲委派机制 核心思想 好处 源码分析 类加载器之间的父子层级关系 双亲委派的体现 前言 上文中提到…

【 限流技术 | 从四大限流算法到Redisson令牌桶实践 】

引言&#xff1a;为什么需要限流&#xff1f;在现代分布式系统中&#xff0c;服务的稳定性是至关重要的。在遇到突发的请求量激增&#xff0c;恶意的用户访问&#xff0c;亦或是请求频率过高给下游服务带来较大压力时&#xff0c;我们常常需要通过缓存、限流、熔断降级、负载均…