文章目录

  • “天上天下,唯我独尊”——单例模式
  • 单例模式简介
  • 单例模式结构
  • 饿汉式
  • 懒汉式
  • 客户端示例
    • 运行结果
  • 单例模式总结
  • 构建型模式 Creational Patterns 小结 Summary

代码仓库
在这里插入图片描述

“天上天下,唯我独尊”——单例模式

你能在电脑上调出两个Windows任务管理器吗?
假设能,如果两个管理器显示的数据相同,那何必要存在两个呢?
如果两个管理器显示的数据不同,那我该相信哪一个呢?

试试看,应该有且仅有一个吧?一个系统里有且仅有一个Windows任务管理器实例供外界访问 。如何保证系统里有且仅有一个实例对象呢?并且能够供外界访问?你可以在系统里定义一个统一的全局变量,但这并不能防止创建多个对象(想一想,为什么?)这就是单例模式的典型应用。

对于一个软件系统中的某些类来说,只有一个实例很重要。假设Windows系统上可以同时调出两个Windows任务管理器,这两个任务管理器显示的都是同样的信息,那势必造成内存资源的浪费;如果这两个任务管理器显示的是不同的信息,这也给用户带来了困惑,到底哪一个才是真实的状态?

单例模式简介

单例模式定义:
确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。

在这里插入图片描述

单例模式结构

单例模式结构非常简单,其UML图如下所示,只包含一个类,即单例类。为防止创建多个对象,其构造函数必须是私有的(外界不能访问)。另一方面,为了提供一个全局访问点来访问该唯一实例,单例类提供了一个公有方法getInstance来返回该实例。

在这里插入图片描述

饿汉式

饿汉式:变量在声明时便初始化。

// 饿汉式(立即加载)
// 饿汉式(Hungry Singleton):程序启动时立即创建对象
class Singleton_Hungry {
public:static Singleton_Hungry* getInstance() {std::cout << "\n[Hungry] 获取单例实例" << std::endl;static Singleton_Hungry instance; // 推荐方法,更安全更现代化return &instance;}void doSomething() const {std::cout << "\n[Hungry] 正在执行任务..." << std::endl;}private:// 禁止外界创建新的实例(私有构造、删除拷贝构造和拷贝赋值)。Singleton_Hungry() {std::cout << "\n[Hungry] 构造函数调用" << std::endl;}~Singleton_Hungry() {std::cout << "\n[Hungry] 析构函数调用" << std::endl;}Singleton_Hungry(const Singleton_Hungry&) = delete;Singleton_Hungry& operator=(const Singleton_Hungry&) = delete;// static Singleton_Hungry* instance;
};// 静态成员初始化 ⚠️头文件定义静态变量,错误!
// 根本原因:违反了单一定义规则(One Definition Rule, ODR)
// 你在头文件中定义了一个静态变量,头文件会被多个.cpp文件包含,这就导致在每个.cpp文件中都定义了一遍,最终会导致链接时的冲突,出现重复定义(multiple definition)错误。
// Singleton_Hungry* Singleton_Hungry::instance = new Singleton_Hungry();

可以看到,我们将构造方法定义为 private,这就保证了其他类无法实例化此类,必须通过 getInstance 方法才能获取到唯一的 instance 实例,非常直观。但饿汉式有一个弊端,那就是即使这个单例不需要使用,它也会在类加载之后立即创建出来,占用一块内存,并增加类初始化时间。就好比一个电工在修理灯泡时,先把所有工具拿出来,不管是不是所有的工具都用得上。就像一个饥不择食的饿汉,所以称之为饿汉式。

懒汉式

懒汉式:先声明一个空变量,需要用时才初始化。例如:

我们先声明了一个 instance 变量,当需要使用时判断此变量是否已被初始化,没有初始化的话才 new 一个实例出来。就好比电工在修理灯泡时,开始比较偷懒,什么工具都不拿,当发现需要使用螺丝刀时,才把螺丝刀拿出来。当需要用钳子时,再把钳子拿出来。就像一个不到万不得已不会行动的懒汉,所以称之为懒汉式

懒汉式解决了饿汉式的弊端,好处是按需加载,避免了内存浪费,减少了类初始化时间。

Singleton.h

// 确保一个类只有一个实例。
// 提供全局访问点,让用户方便访问// 懒汉式(线程安全,推荐写法)
// 懒汉式(Lazy Singleton):延迟创建对象(用时才创建)
class Singleton_Lazy {
public:// 第一次 调用时,执行内部的 lambda 表达式,创建一个新的单例对象。// 后续所有次 调用时,都直接跳过这个 lambda,不再执行创建对象的操作。static Singleton_Lazy* getInstance() {// template< class Callable, class... Args >// void call_once(std::once_flag& flag, Callable&& f, Args&&... args);// 确保给定的**可调用对象(如lambda表达式)**仅被调用一次。// 如果多个线程同时执行该行代码,也能确保只有一个线程实际执行了初始化操作,其他线程则会等待,直到第一次调用完成后再继续执行,且不会重复执行初始化操作。// flag:一个标志位,标识对应的初始化是否已经完成。// Callable:一个可调用对象(比如lambda表达式或函数),代表真正要执行的初始化动作。std::call_once(initFlag, []() {std::cout << "\n[Lazy] 创建新的实例" << std::endl;// instance = std::make_unique<Singleton_Lazy>(); // ✅ 更安全,更推荐instance.reset(new Singleton_Lazy());});std::cout << "\n[Lazy] 获取单例实例" << std::endl;return instance.get();}void doSomething() const {std::cout << "\n[Lazy] 正在执行任务..." << std::endl;}private:Singleton_Lazy() {std::cout << "\n[Lazy] 构造函数调用" << std::endl;}~Singleton_Lazy() {std::cout << "\n[Lazy] 析构函数调用" << std::endl;}// 拷贝构造函数(Copy Constructor)// 当使用一个已存在对象初始化另一个新对象时,调用拷贝构造函数Singleton_Lazy(const Singleton_Lazy&) = delete;// 拷贝赋值运算符(Copy Assignment Operator)// 当使用一个已存在的对象去给另一个已存在的对象赋值时调用。Singleton_Lazy& operator=(const Singleton_Lazy&) = delete;// ⚠️一定要保留这两个声明!static std::unique_ptr<Singleton_Lazy> instance;static std::once_flag initFlag;// 🔑加上这一句:// 允许 std::unique_ptr 调用私有析构函数friend class std::default_delete<Singleton_Lazy>;
};

Singleton.cpp

#include "Singleton.h"std::unique_ptr<Singleton_Lazy> Singleton_Lazy::instance = nullptr;
std::once_flag Singleton_Lazy::initFlag;

客户端示例

#define THREAD_NUM 6
#include <iostream>
#include <pthread.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "Singleton.h"pid_t GetThreadId() {// syscall 是一个系统调用接口,可以让你直接调用操作系统提供的底层功能。// SYS_gettid 是 Linux 系统调用号,表示获取当前线程的线程ID(gettid)。// syscall(SYS_gettid) 实际上是执行 gettid() 系统调用的操作,返回当前线程的线程ID。// 该调用返回当前线程的线程ID,通常与 pthread_self() 的返回值相同,但是 gettid 是返回内核级线程ID,而 pthread_self() 返回的是 POSIX 线程库级别的线程ID// SYS_gettid 是一个常量,表示获取当前线程ID的系统调用号。// 每个系统调用都有一个唯一的编号(常量),用于标识该系统调用。SYS_gettid 对应的是获取线程ID的操作。return syscall(SYS_gettid);
}void* callSingleton_Lazy(void* arg) {int threadID = *(int*)arg;Singleton_Lazy *s = Singleton_Lazy::getInstance();printf("[Lazy] 线程编号: %d, 实例地址: %d\n", threadID, GetThreadId());// printf("[Hungry] 线程编号: %d, 实例地址: %p\n", threadID, s);return 0;
}void* callSingleton_Hungry(void* arg) {// 将arg 从 void* 类型的通用指针强制转换成 int*类型的指针, 然后对转换后的指针解引用,取出实际的整型数值(即线程编号)。int threadID = *(int*)arg;Singleton_Hungry *s = Singleton_Hungry::getInstance();printf("[Hungry] 线程编号: %d, 实例地址: %d\n", threadID, GetThreadId());// printf("[Hungry] 线程编号: %d, 实例地址: %p\n", threadID, s);return 0;
}int main() {pthread_t threads_pool[THREAD_NUM];int tids[THREAD_NUM], params[THREAD_NUM];for(int i = 0; i < THREAD_NUM; i++) {params[i] = i; // 独立参数,避免竞争/*int pthread_create(pthread_t *restrict thread,const pthread_attr_t *restrict attr,void *(*start_routine)(void *),void *restrict arg);*/// 前半部分线程调用懒汉式单例if(i < THREAD_NUM / 2)tids[i] = pthread_create(&threads_pool[i], NULL, callSingleton_Lazy, (void*)&params[i]);else // 后半部分线程调用饿汉式单例tids[i] = pthread_create(&threads_pool[i], NULL, callSingleton_Hungry, (void*)&params[i]);// On success, pthread_create() returns 0; on error, it returns an error number, and the contents of *thread are undefined.if(tids[i]) {printf("Error: unable to create thread.\n");exit(-1);}}for(int i = 0; i < THREAD_NUM; i++) {// On success, pthread_join() returns 0; on error, it returns an error numbertids[i] = pthread_join(threads_pool[i], NULL);if(tids[i]) {printf("Error: unable to join thread.\n");exit(-1);}}printf("main exiting.\n");return 0;
}

运行结果

在这里插入图片描述

单例模式总结

单例模式让一个类同时负责了『业务功能』和『自身的创建与生命周期管理』两个职责。
在这里插入图片描述
在这里插入图片描述

构建型模式 Creational Patterns 小结 Summary

在这里插入图片描述

之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!

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

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

相关文章

静电释放检测漏报率↓85%!陌讯多模态融合算法在电子厂ESD防护实战解析

​摘要​​ 基于边缘计算的静电释放(ESD)视觉检测方案&#xff0c;通过多模态融合技术显著提升复杂场景鲁棒性。实测显示&#xff1a;在电子元件装配线上&#xff0c;ESD事件检测mAP0.5达89.1%&#xff0c;较基线模型提升28.3%。一、行业痛点&#xff1a;ESD检测的隐形危机根据…

RAL-2025 | “藏宝图”驱动的具身导航!HAM-Nav:基于手绘地图引导的机器人导航

作者&#xff1a;Aaron Hao Tan, Angus Fung, Haitong Wang, Goldie Nejat单位&#xff1a;多伦多大学机械与工业工程系论文标题&#xff1a;Mobile Robot Navigation Using Hand-Drawn Maps: A Vision Language Model Approach出版信息&#xff1a;IEEE ROBOTICS ANDAUTOMATI…

Vue.js 与后端技术结合开发指南

Vue.js 作为现代化的前端框架&#xff0c;可以与多种后端技术完美结合&#xff0c;构建全栈应用。下面我将详细介绍 Vue 可以与哪些后端技术结合开发&#xff0c;并提供可视化示例。Vue 可结合的后端技术概览主流组合方案对比后端技术适合场景优点缺点学习曲线Node.js全栈JavaS…

逻辑回归在银行贷款审批中的应用:参数选择与实践

目录 一、数据背景与预处理 1.数据前五行 2.数据预处理步骤 二、逻辑回归的正则化参数选择 1.交叉验证选择最优C 2.为什么选择召回率作为评估指标&#xff1f; 三、参数选择的核心结论 四、后续优化方向 在银行贷款审批场景中&#xff0c;准确判断贷款人是否符合贷款条…

数据结构前篇 - 深入解析数据结构之复杂度

目录一、数据结构前言1.1 数据结构1.2 算法二、算法效率2.1 复杂度的概念三、时间复杂度3.1 大O的渐进表示法3.2 时间复杂度计算示例3.2.1 示例13.2.2 示例23.2.3 示例33.2.4 示例43.2.5 示例53.2.6 示例63.2.7 示例7四、空间复杂度4.1 空间复杂度计算示例4.1.1 示例14.1.2 示例…

Master Prompt:AI时代的万能协作引擎

1. Master Prompt&#xff1a;为什么它正在重塑AI协作范式大模型落地的最大痛点不是技术本身&#xff0c;而是人机协作的断裂。当企业采购了昂贵的AI系统&#xff0c;却发现输出内容反复偏离预期&#xff0c;团队成员抱怨“AI总听不懂我要什么”&#xff0c;这种场景每天在无数…

《Kubernetes部署篇:基于Kylin V10+ARM架构CPU使用containerd部署K8S 1.33.3容器板集群(一主多从)》

总结:整理不易,如果对你有帮助,可否点赞关注一下? 更多详细内容请参考:企业级K8s集群运维实战 一、架构图 如下图所示: 二、环境信息 基于x86_64+aarch64架构使用containerd部署K8S 1.33.3集群资源合集(一主多从) 2、部署规划 主机名 K8S版本 系统版本 CPU架构 内核版…

一次性接收大量上传图片,后端优化方式

文章目录1. 分块接收与流式处理2. 异步处理3. 内存映射与临时文件4. 数据库优化5. 缓存策略6. 压缩与格式优化7. 限流与并发控制8. 分布式存储9. 响应优化10. 监控与错误处理11. 数据库连接池优化1. 分块接收与流式处理 使用流式处理避免将所有图片加载到内存中&#xff1a; …

二分查找(基础)

竞赛中心 - 蓝桥云课 #include <iostream> #include<bits/stdc.h> using namespace std; #define int long long int N; struct NO {int A,B; }a[10001]; bool ok(int V) {for (int i 0; i < N; i){if (a[i].A / V ! a[i].B){return false;}}return true; } …

流式编程学习思路

流式编程学习思路 作为Java初级工程师,想要掌握流式编程并向高级工程师进阶,需要从基础到进阶逐步掌握,结合实战场景深化理解。以下是为你量身定制的学习清单和思路: 一、基础阶段:吃透 Java Stream 核心API 1. 掌握 Stream 的基本概念 什么是 Stream:理解它与集合(Co…

13-14linux三剑客grep,sed,awk

目录 三剑客支持扩展正则写法 grep命令 sed命令 sed指定行查找&#xff1a; sed模糊过滤文件内容 sed之删除&#xff1a; sed之替换&#xff1a; sed追加插入替换&#xff1a; sed后向引用&#xff1a; awk命令 awk按照行查找 awk模糊过滤文件内容 awk取列 awk指…

损失函数和调度器相关类代码回顾理解 |nn.CrossEntropyLoss\CosineAnnealingLR

目录 nn.CrossEntropyLoss CosineAnnealingLR nn.CrossEntropyLoss loss_func nn.CrossEntropyLoss(reduction"sum") 定义nn.CrossEntropyLoss交叉熵损失函数&#xff0c;reduction参数设置为"sum"&#xff0c;表示将所有样本的损失相加。reduction 参…

中国不同类型竹林分布数据

中国竹林分布的主要特点简介&#xff1a;总体分布格局&#xff1a;核心区域&#xff1a; 主要分布在长江流域及以南的广大亚热带和热带地区。北界&#xff1a; 大致以黄河流域为北界&#xff0c;但天然成片竹林在秦岭-淮河一线以南才比较普遍。人工引种或特殊小环境下&#xff…

Sqlserver备份恢复指南-完整备份恢复

博主会用简单清晰的方式&#xff0c;带你系统学习使用T-SQL命令行的方式 给SQL Server 做备份与恢复。我们按照从零开始、逐步深入的路线来讲解&#xff01; 完整备份恢复-差异增量备份恢复-事务日志备份恢复 &#x1f538; SQL Server 备份类型&#xff1a;类型说明完整备份&a…

AI 调酒师上岗!接管酒吧吧台

7月29日&#xff0c;马老师的 HHB 音乐酒吧在阿里巴巴西溪园区正式开业&#xff0c;开业这天迎来了一位神秘嘉宾“AI 调酒师”&#xff01; 这位 AI 调酒师不仅能根据你的MBTI、今日情绪、星座运势、江湖花名等为你特调一杯鸡尾酒&#xff0c;还能为这杯酒配上故事和诗文。 点…

【C++进阶】一文吃透静态绑定、动态绑定与多态底层机制(含虚函数、vptr、thunk、RTTI)

【C进阶】一文吃透静态绑定、动态绑定与多态底层机制&#xff08;含虚函数、vptr、thunk、RTTI&#xff09;作者&#xff1a;你的C教练 日期&#xff1a;2025-08-01目录 静态绑定 vs 动态绑定非虚函数的三大坑多态的四要素虚析构函数为什么必须写&#xff1f;探秘 vptr/vftable…

VUE基础知识2

1.计算属性&#xff1a;使用计算属性来描述依赖响应式状态的复杂逻辑。关键字computed:{}//计算属性&#xff0c;使用的时候和函数方法不一样&#xff0c;不需要加括号。简单来说就是模板方法的复杂逻辑放到了计算属性中去。2.计算属性缓存VS方法&#xff1a;计算属性值会基于其…

在PyCharm中将现有Gitee项目重新上传为全新项目

如果你想将当前本地的Gitee项目重新上传为一个全新的Gitee项目&#xff08;保留本地代码但断开与原仓库的关联&#xff09;&#xff0c;可以按照以下步骤操作&#xff1a; 删除旧的Git远程仓库关联 打开PyCharm&#xff0c;进入你的项目 点击顶部菜单 Git > Manage Remotes …

设计模式1:创建型模式

设计模式1&#xff1a;创建型模式 设计模式2&#xff1a;结构型模式&#xff08;编写中&#xff09; 设计模式3&#xff1a;行为型模式&#xff08;编写中&#xff09; 前言 设计模式是软件开发中经过验证的可复用解决方案&#xff0c;它们源自实践、提炼于经验&#xff0c;并…

React--》规划React组件库编码规范与标准 — Button篇

目前前端组件化已经成为前端开发的核心思想之一&#xff0c;在这篇文章中将深入探讨如何规划一个规范的Button组件&#xff0c;让它不仅能高效支持不同的功能需求还能确保跨项目、跨团队的一致性&#xff0c;抛砖引玉的方式引出后面组件库的其他组件的开发&#xff01; 目录 B…