设计模式 | 单例模式——饿汉模式 懒汉模式

单例模式

文章目录

  • 单例模式
    • 一、饿汉模式(Eager Initialization)
      • 1. 定义
      • 2. 特点
      • 3. 饿汉单例模式(定义时-类外初始化)
      • 4. 实现细节
    • 二、懒汉模式(Lazy Initialization)
      • 1. 定义
      • 2. 特点
      • 3. 懒汉单例模式(第一次调用时-初始化)
      • 4. 多线程不安全(需加锁)
    • 三、对比 & 使用建议

一、饿汉模式(Eager Initialization)

1. 定义

类加载时就创建实例,不管你用不用,先创建再说。

2. 特点

  • 线程安全(因为类加载是线程安全的)
  • 启动时就分配资源,资源消耗可能较大

3. 饿汉单例模式(定义时-类外初始化)

#include <iostream>class TaskQueue {
public:// 静态方法:获取唯一实例指针static TaskQueue* getInstance() {return m_taskQ;  // 返回静态成员变量指针}// 删除拷贝构造函数:防止复制实例(例如 TaskQueue b = a)TaskQueue(const TaskQueue&) = delete;// 删除赋值运算符:防止赋值复制(例如 a = b)TaskQueue& operator=(const TaskQueue&) = delete;private:// 默认构造函数私有化:禁止类外部构造对象// 外部无法通过 new TaskQueue() 或 TaskQueue t; 构造对象TaskQueue() = default;// 静态成员变量声明:用于保存唯一实例的指针static TaskQueue* m_taskQ;
};// ⚠️ 类外定义并初始化静态成员变量:这一行非常关键!
// ✅ 这是 TaskQueue 类的“静态成员变量定义+初始化”
// ✅ new TaskQueue 调用了 private 构造函数,但因为这是类自己的代码(初始化自己的静态成员),所以**允许访问私有成员**
TaskQueue* TaskQueue::m_taskQ = new TaskQueue;
// --------------------------------------------
// ⬆️ 虽然这行“写在类外”(语法上),但它是类的一部分(静态成员初始化),它仍然被认为是类自己的代码(类内部行为),所以可以访问私有构造函数。
// C++ 标准允许它访问类的 private 构造函数。
// 所以不会报错,而是合法的。int main() {// 获取单例对象的指针TaskQueue* q1 = TaskQueue::getInstance();TaskQueue* q2 = TaskQueue::getInstance();// 打印地址验证是否为同一实例std::cout << "q1 地址: " << q1 << std::endl;std::cout << "q2 地址: " << q2 << std::endl;// 输出地址肯定一样return 0;
}

注意:

// 静态成员变量定义和初始化(在类外完成)
TaskQueue* TaskQueue::m_taskQ = new TaskQueue;

这句代码在程序启动时就执行,立即创建了 TaskQueue 的唯一实例:

  1. 是静态变量,生命周期贯穿整个程序;
  2. 实例在任何 getInstance() 调用之前就已创建完成;
  3. getInstance() 只是简单地返回这个已创建好的指针。

因此,它就是一个标准的饿汉单例模式实现。

4. 实现细节

  1. 为什么TaskQueue* TaskQueue::m_taskQ = new TaskQueue;属于类内访问,可以访问private构造函数?
    是因为它是“静态变量”?“私有变量”?还是“初始化”这件事本身?
条件是否是关键解释
这是类的成员定义✅ 是关键初始化 TaskQueue::m_taskQ 是类的一部分,因此有权限访问类的私有成员
static 成员❌ 不是核心原因虽然需要类外初始化,但并不是 static 带来了访问权限
private 变量❌ 更不是原因private 表示“只能被类的代码访问”,而这行被视为类的代码

不管是 static 还是 private,关键原因在于:这是类的“成员变量定义”,属于类的内部实现,因此它拥有类的访问权限。

  1. 延申:若把 new TaskQueue 写在 main()

❌ 非法代码(main 函数中访问私有构造函数)

int main() {TaskQueue* q = new TaskQueue();  // ❌ 错!构造函数是 private
}

为什么报错?

  • main() 是类外部的普通代码。
  • 它不是类成员,不被视为类内部实现。
  • 因此没有权限访问私有构造函数,编译器会直接报错。

✅ 合法代码(类外定义静态成员时调用私有构造函数)

TaskQueue* TaskQueue::m_taskQ = new TaskQueue;  // ✅ 对!

为什么合法?

  • 这是类在定义和初始化自己的静态成员变量。
  • 虽然代码写在类外,但它被视为类的一部分(属于 TaskQueue 类实现)。
  • 所以有权访问 private 构造函数。
  • C++ 语法明确允许这种访问。

二、懒汉模式(Lazy Initialization)

1. 定义

在第一次访问时才创建实例,延迟到真正需要的时候再进行初始化。

2. 特点

  • 延迟加载:只有在首次调用 getInstance() 时才会创建实例,节省系统资源;
  • 线程不安全(默认实现),但可以通过加锁、双重检查、std::call_once 等方式实现线程安全
  • 相较于饿汉模式,更灵活、更节省资源,但实现稍复杂。

3. 懒汉单例模式(第一次调用时-初始化)

#include <iostream>class TaskQueue {
public:// ❌ 没有加锁,线程不安全 ******不同点******static TaskQueue* getInstance() {if (m_taskQ == nullptr) {    m_taskQ = new TaskQueue(); // ❌不安全,可能多个线程同时执行这里,创建多个实例}return m_taskQ;}TaskQueue(const TaskQueue&) = delete;TaskQueue& operator=(const TaskQueue&) = delete;private:TaskQueue() = default;static TaskQueue* m_taskQ;
};// 初始化静态实例指针 ******不同点******
TaskQueue* TaskQueue::m_taskQ = nullptr;int main() {TaskQueue* q1 = TaskQueue::getInstance();TaskQueue* q2 = TaskQueue::getInstance();std::cout << "q1 地址: " << q1 << std::endl;std::cout << "q2 地址: " << q2 << std::endl;// 输出地址一样(如果线程不冲突)return 0;
}

4. 多线程不安全(需加锁)

线程冲突时,多个线程可能在getInstance()创建多个对象,需要加锁!!!

三、对比 & 使用建议

对比项饿汉模式(Eager Singleton)懒汉模式(Lazy Singleton)
实例创建时机程序启动时 / 类加载时立即创建第一次调用 getInstance() 时才创建
资源占用无论是否使用都会占用资源仅在需要时才占用资源,更节省内存
线程安全✅ 天然线程安全(由 C++ 静态初始化保证)❌ 默认线程不安全,需手动加锁处理
实现难度实现简单,逻辑清晰实现复杂(涉及锁、双检、或 call_once)
性能开销启动时略高,占用资源可能浪费每次调用 getInstance() 可能涉及锁(效率略低)
适用场景实例始终会用到,资源占用可接受实例可能不一定会用到,或实例化代价较高
常用实现类外初始化静态成员指针(如:new Singleton;内部判断是否为 null + 加锁后 new Singleton();
示例构造代码TaskQueue* m = new TaskQueue;(类外直接构造)if (!m) m = new TaskQueue;(函数内延迟构造)
可扩展性不容易扩展为参数化构造初始化时可自定义参数(但需额外设计)
使用场景推荐模式
实例一定会被频繁使用✅ 饿汉模式(简单稳定)
实例创建代价高或可能不用✅ 懒汉模式(延迟创建)
多线程访问高频✅ 饿汉 或 call_once 懒汉
希望按需控制生命周期✅ 懒汉更灵活

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

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

相关文章

dify本地部署及添加ollama模型(ubuntu24.04)

说明&#xff1a;ubuntu是虚拟机的&#xff0c;用的桥接模式&#xff0c;与本地同局域网不同ip地址。 参考VM虚拟机网络配置&#xff08;ubuntu24桥接模式&#xff09;&#xff1a;配置静态IP前提&#xff1a;需要有docker及docker-compose环境 参考ubuntu24安装docker及docker…

Python爬虫实战:研究multiprocessing相关技术

一、引言 1.1 研究背景与意义 随着互联网信息的爆炸式增长,网络爬虫已成为获取海量数据的重要工具。传统的单线程爬虫在面对大规模数据采集任务时效率低下,无法充分利用现代计算机多核 CPU 的优势。多线程爬虫虽然在一定程度上提高了效率,但受限于 Python 的全局解释器锁(…

6.18 redis面试题 日志 缓存淘汰过期删除 集群

Redis有哪2种持久化方式&#xff1f;分别的优缺点是什么&#xff1f; Redis 的重写 AOF 过程是由后台子进程 bgrewriteaof 来完成的。 过期删除策略和内存淘汰策略有什么区别&#xff1f; 内存淘汰策略是在内存满了的时候&#xff0c;redis 会触发内存淘汰策略&#xff0c;来淘…

什么时候会发生内存泄漏?

1. 内存泄漏是什么&#xff1f; 定义&#xff1a;内存泄漏是指程序中的对象已经不再需要&#xff0c;但由于被其他对象错误引用&#xff0c;导致垃圾回收器&#xff08;GC&#xff09;无法回收它&#xff0c;从而长期占用内存空间的现象。 2. 内存泄漏的危害 问题具体表现内存…

用RSA算法模拟类的适配器模式

“RAS算法”这个术语本身并不常见或标准&#xff0c;它可能指向两个主要领域的不同概念&#xff0c;具体取决于上下文&#xff1a; 更可能是拼写错误&#xff1a;指 RSA 算法&#xff08;密码学&#xff09; 这是最常见的情况。 “RAS” 极有可能是 “RSA” 的拼写错误。RSA 算…

CARSIM-与C#自动化测试方案

using System; using System.Runtime.InteropServices; using System.Collections.Generic;namespace CarSimAutomation {/// <summary>/// CarSim COM 自动化测试接口/// 封装所有 CarSim COM 功能用于自动化测试/// </summary>[ComVisible(true)][ClassInterface…

企微CRM系统中的任务分配与效率提升技巧

在数字化管理时代&#xff0c;企业微信(企微)与CRM系统的深度融合&#xff0c;为企业提供了更高效的客户管理与团队协作方案。企微CRM软件不仅整合了客户沟通、销售跟进、数据分析等功能&#xff0c;还能通过智能任务分配优化团队效率。本文将深入探讨企微CRM管理系统的任务分配…

day66—BFS—最短的桥(LeetCode-934)

题目描述 给你一个大小为 n x n 的二元矩阵 grid &#xff0c;其中 1 表示陆地&#xff0c;0 表示水域。 岛 是由四面相连的 1 形成的一个最大组&#xff0c;即不会与非组内的任何其他 1 相连。grid 中 恰好存在两座岛 。 你可以将任意数量的 0 变为 1 &#xff0c;以使两座…

FramePack 安装指南(中文)

FramePack 安装指南&#xff08;中文&#xff09; -Windows FramePack 是最前沿的 AI 视频生成框架&#xff0c;以极小的硬件需求颠覆视频创作&#xff01;它能在仅 6GB 笔记本 GPU 内存上&#xff0c;驱动 13B 模型以 30 FPS 生成超长 120 秒视频&#xff0c;几乎无内容限制&…

Redis Sentinel 非集群模式高可用部署指南

1. Sentinel 在非集群模式的定位 一句话&#xff1a;在单主多从架构中&#xff0c;用 Sentinel 替你盯哨——探测故障、选举新主、通知客户端。 核心四职能&#xff1a; 职能作用点Monitoring定时 PING 主从&#xff0c;自身也互相探测Notification通过日志/PubSub/外部调用报…

2025Java面试八股文

文章目录 Java基础JVM多线程SpringSpring Boot数据库与SQL分布式系统其他 Java基础 自动装箱与拆箱&#xff1a;Java中基础数据类型与包装类之间的转换。例如&#xff0c;Integer x 1; 是装箱&#xff0c;int y x; 是拆箱。Object类常用方法&#xff1a;如clone()、getClass…

宝塔安装nginx-rtmp,音视频直播

前置&#xff1a;需要自己开发音视频直播&#xff0c; 注意不是实时音视频&#xff0c;不是一对一视频聊天&#xff0c;不是视频会议 方案有 srs &#xff0c;nginx-rtmp&#xff0c;live555&#xff0c;node-media-server&#xff0c;EasyDarwin等 今天是说 nginx-rtmp 怎么…

基于微信小程序和深度学习的宠物照片拍摄指导平台的设计与实现

文章目录 摘要前言绪论1. 课题背景2. 国内外现状与趋势2.1 国内研究现状2.2 国外研究现状2.3 发展趋势3. 课题内容相关技术与方法介绍1. 微信小程序开发技术2. 深度学习模型选型2.1 MobileNetV22.2 ResNet-503. 系统架构设计4. 关键技术实现4.1 实时拍摄指导4.2 多模态建议生成…

web布局02

Web 发展的每个不同时期都有新的技术为 Web 布局提供支持&#xff0c;但不管是哪个时期&#xff0c;Web 布局相关的概念和术语都是相同的。如果你想彻底或者更好地掌握 Web 布局&#xff0c;那么首先需要对 Web 布局相关的技术术语有所了解。 在这一节中&#xff0c;我们一起来…

Mac电脑 窗口分屏管理 Magnet Pro

Magnet Pro Mac&#xff0c;是一款功能强大的窗口分屏管理工具&#xff0c;具有多种布局模式、窗口布局功能和其他工具&#xff0c;可以帮助您高效地进行多任务处理和管理工作。 拖动窗口到边缘&#xff0c;可将窗口大小调整到屏幕的一半。拖动窗口到角落&#xff0c;可将窗口…

http2与websocket关系

HTTP/2 和 WebSocket 协议本身确实不兼容&#xff0c;不能像在 HTTP/1.1 中那样用标准 WebSocket 协议&#xff08;ws:// / wss://&#xff09;进行升级握手。但这事儿细节比较多&#xff0c;下面详细讲讲&#xff1a; ✅ HTTP/2 与 WebSocket 的关系 HTTP/2 不直接支持 WebSo…

LoRA 与 CoT 冲突吗

对于一个具有CoT 能力的模型来说&#xff0c;采用普通的数据对其进行LoRA 微调可能会使原模型丢失CoT 能力&#xff0c;从而我们进行思考如下 CoT 与 LoRA 的“冲突”理解 目标不完全一致 导致的效果优化方向&#xff1a; CoT 侧重于提高推理能力和可解释性&#xff0c;它鼓励…

Python爬虫-爬取票牛明星演唱会数据,进行数据分析

前言 本文是该专栏的第61篇,后面会持续分享python爬虫干货知识,记得关注。 本文,笔者以“票牛”平台为例。基于Python爬虫,采集“票牛”平台的明星演唱会(包含“演出城市,演出票价,演出时间”等等)的数据。 废话不多说,具体实现思路和详细逻辑,笔者将在正文结合完整…

uniapp的video遮盖了popup

video的默认层级太高&#xff0c;导致popup弹出的时候&#xff0c;部分被video遮挡了 可以利用cover-view&#xff0c;将popup以及内部所有的标签&#xff0c;全都换成cover-view&#xff0c;然后用一个变量控制其显隐 比如原始&#xff1a; 现在&#xff1a;

java面试题02访问修饰符有哪些?区别是什么?

访问修饰符是面向对象编程中实现封装的核心机制&#xff0c;用于控制类、属性、方法等成员的可见性&#xff08;可访问范围&#xff09;。不同的访问修饰符决定了其他类或代码在何处可以访问这些成员。 主要的访问修饰符及其区别如下&#xff08;以 Java 和 C# 为代表&#xf…