【设计模式】单件模式

news2025/3/28 6:44:17

七、单件模式

单件(Singleton) 模式也称单例模式/单态模式,是一种创建型模式,用于创建只能产生 一个对象实例 的类。该模式比较特殊,其实现代码中没有用到设计模式中经常提起的抽象概念,而是使用了一种比较特殊的语法结构(私有的构造函数)实现类的定义。
该模式比较好理解,也比较常用,但要设计得好并不容易,有许多细节需要把握,本章将详细探讨。

7.1 单件类的基本概念和实现

在一个软件中,往往存在一些比较特殊的类,希望在整个软件中只存在该类的一个对象(实例)。以闯关打斗类游戏为例,策划要求游戏中有一些配置选项,能够对游戏的声音大小、图像质量等进行配置,为此,要创建一个游戏配置相关的类 GameConfig,代码如下:

// 游戏配置相关类
class GameConfig {
    // …待增加
};

为了使用该类,需要创建该类相关的对象,于是,可以通过下面的代码创建一个类对象:

GameConfig g_config1;

当然,还可以创建该类的另外一个对象,甚至还可以创建更多的该类对象:

GameConfig g_config2;

但从项目需求来讲,整个游戏中只应该存在一个 GameConfig 类对象(对象可以在栈上创建也可以用 new 在堆上创建),该对象相当于一个全局的 GameConfig 类对象,统管游戏的配置。创建多个 GameConfig 类对象可能会造成混乱或程序逻辑不正确(也可能造成性能问题)。

单件类的设计原则

C++ 软件开发专家 Scott Meyers 曾经说过:“要使接口或者类型易于正确使用,难以错误使用。” 因此,保证类的单一实例化应该由类的设计者实现,而非依赖使用者的自律。

单件类的实现代码

// 游戏配置相关类
class GameConfig {
private:
    GameConfig() {};
    GameConfig(const GameConfig& tmpobj);
    GameConfig& operator=(const GameConfig& tmpobj);
    ~GameConfig() {};

public:
    static GameConfig* getInstance() {
        if (m_instance == nullptr) {
            m_instance = new GameConfig();
        }
        return m_instance;
    }

private:
    static GameConfig* m_instance;  // 指向本类对象的指针
};

GameConfig* GameConfig::m_instance = nullptr;  // 类外初始化静态成员变量

关键设计点

  1. 私有构造函数:禁止在类外部创建对象(包括栈和堆)。

    GameConfig* g_gc = new GameConfig;  // 非法
    GameConfig gc;                      // 非法
    
  2. 静态工厂方法:通过 getInstance() 创建唯一实例,首次调用时初始化。

  3. 禁止拷贝和赋值

    GameConfig g_gc3(*g_gc);   // 非法(拷贝构造)
    (*g_gc2) = (*g_gc);       // 非法(拷贝赋值)
    
  4. 私有析构函数:禁止外部手动释放对象。

    delete g_gc2;  // 非法
    

7.2 单件类在多线程中可能导致的问题

若多个线程同时调用 getInstance(),可能导致 多次实例化。例如:

  • 线程 1 执行 if (m_instance == nullptr) 后被挂起。
  • 线程 2 同样通过条件判断并创建实例。
  • 线程 1 恢复后再次创建实例,导致两个对象存在。

解决方案 1:加锁

static GameConfig* getInstance() {
    std::lock_guard<std::mutex> gcguard(my_mutex);  // 加锁
    if (m_instance == nullptr) {
        m_instance = new GameConfig();
    }
    return m_instance;
}

缺点:每次调用都需加锁,影响性能(仅首次初始化需要保护)。

解决方案 2:双重锁定(Double-Check Locking)

static GameConfig* getInstance() {
    if (m_instance == nullptr) {  // 第一重检查(无锁)
        std::lock_guard<std::mutex> lock(m_mutex);  // 加锁
        if (m_instance == nullptr) {  // 第二重检查(加锁后)
            m_instance = new GameConfig();
        }
    }
    return m_instance;
}

潜在问题:内存访问重排序可能导致未初始化的对象被使用(需结合 std::atomic 和内存栅栏解决)。

推荐方案

主线程 中提前初始化单件对象,避免多线程竞争:

int main() {
    GameConfig::getInstance();  // 提前初始化
    // 创建其他线程...
    return 0;
}

7.3 饿汉式与懒汉式

单件类的初始化方式分为两种:

饿汉式(Eager Initialization)

特点:程序启动时立即创建实例,线程安全。

class GameConfig {
    static GameConfig* m_instance = new GameConfig();  // 类外直接初始化
};

缺点:可能提前消耗资源(即使未使用)。

懒汉式(Lazy Initialization)

特点:首次调用 getInstance() 时创建实例,延迟加载。

class GameConfig {
    static GameConfig* getInstance() {
        if (m_instance == nullptr) {
            m_instance = new GameConfig();
        }
        return m_instance;
    }
};

缺点:多线程下需额外处理同步问题。

7.4 单件类对象内存释放问题

单件对象的生命周期通常与程序一致,但可通过以下方式手动释放:

方案 1:手动调用释放函数

class GameConfig {
public:
    static void freeInstance() {
        if (m_instance != nullptr) {
            delete m_instance;
            m_instance = nullptr;
        }
    }
};

方案 2:嵌套类自动释放(Garbo 模式)

class GameConfig {
private:
    class Garbo {
    public:
        ~Garbo() {
            if (GameConfig::m_instance != nullptr) {
                delete GameConfig::m_instance;
                GameConfig::m_instance = nullptr;
            }
        }
    };

    static Garbo garboobj;  // 静态嵌套类对象
};

GameConfig::Garbo GameConfig::garboobj;  // 类外初始化

原理:静态对象 garboobj 的析构函数会在程序结束时自动调用,释放单件对象。

7.5 单件类定义、UML 图及另外一种实现方法

单件模式定义

意图:保证一个类仅有一个实例,并提供全局访问点。

UML 图

GameConfig
-_m_instance: GameConfig // 静态成员
+getInstance()
-GameConfig()

另一种实现:返回局部静态变量引用

class GameConfig {
private:
    GameConfig() {};
    ~GameConfig() {};

public:
    static GameConfig& getInstance() {
        static GameConfig instance;  // 局部静态变量
        return instance;
    }
};

优点

  • 代码简洁,自动处理线程安全(C++11 保证局部静态变量初始化的线程安全)。
  • 实例在首次调用时创建,析构在程序结束时自动执行。

总结

  • 单件类 vs 全局变量:单件类通过封装保证单一实例,全局变量无法阻止多实例。
  • 多线程注意事项:优先在主线程初始化,或使用 C++11 局部静态变量特性。
  • 扩展限制:单件类难以继承,需谨慎设计。

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

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

相关文章

Elasticsearch + Docker:实现容器化部署指南

Elasticsearch是一款强大的分布式搜索和分析引擎&#xff0c;广泛应用于日志分析、全文检索、实时数据分析等场景。而Docker作为一种轻量级的容器化技术&#xff0c;能够帮助开发者快速部署和管理应用。将Elasticsearch与Docker结合&#xff0c;不仅可以简化部署流程&#xff0…

win32汇编环境,网络编程入门之十一

;win32汇编环境,网络编程入门之十一 ;在上一教程里&#xff0c;我们学习了如何读取大容量的网页内容&#xff0c;在这一教程里&#xff0c;我们学习一下如何在wininet或winhttp机制中提取网页中的超链接 ;>>>>>>>>>>>>>>>>>…

穿越之程序员周树人的狂人日记Part3__人机共生纪元

穿越之程序员周树人的狂人日记Part3__人机共生纪元 代码知识点&#xff1a;协程、内存管理、版本控制 故事一【协程陷阱】择偶标准的多核运算 故事二【内存泄漏】中产幻觉的垃圾回收 故事三【版本控制】人设仓库的强制推送 故事四【容器化生存】&#xff1a;员工生存之现状 静夜…

后端——AOP异步日志

需求分析 在SpringBoot系统中&#xff0c;一般会对访问系统的请求做日志记录的需求&#xff0c;确保系统的安全维护以及查看接口的调用情况&#xff0c;可以使用AOP对controller层的接口进行增强&#xff0c;作日志记录。日志保存在数据库当中&#xff0c;为了避免影响接口的响…

循环查询指定服务器开放端口(Python)

循环查询指定服务器开放端口列表 # Time : 2025/3/22 # Author : cookie # Desc :import socket import concurrent.futures from datetime import datetime# 设置目标IP和端口范围 target_ip input("请输入目标IP地址: ") start_port int(input("请输入…

【STM32】第一个工程的创建

目录 1、获取 KEIL5 安装包2、开始安装 KEIL52.1、 激活2.2、安装DFP库 3、工程创建4、搭建框架5、开始编写代码 1、获取 KEIL5 安装包 要想获得 KEIL5 的安装包&#xff0c;在百度里面搜索“KEIL5 下载”即可找到很多网友提供的下载文件&#xff0c;或者到 KEIL 的官网下载&a…

SpringBoot+策略模式+枚举类,优雅消除if-else

需求分析 公司做物联网系统的&#xff0c;使用nettry进行设备连接&#xff0c;对设备进行数据采集&#xff0c;根据设备的协议对数据进行解析&#xff0c;解析完成之后存放数据库&#xff0c;但是不同厂家的设备协议不同。公司系统使用了使用了函数式编程的去写了一个解析类&am…

前端框架学习路径与注意事项

学习前端框架是一个系统化的过程&#xff0c;需要结合理论、实践和工具链的综合掌握。以下是学习路径的关键方面和注意事项&#xff1a; 一、学习路径的核心方面 1. 基础概念与核心思想 组件化开发&#xff1a;理解组件的作用&#xff08;复用性、隔离性&#xff09;、组件通信…

Linux驱动开发-①platform平台②MISC字符驱动框架③input框架

Linux驱动开发-①platform平台②MISC字符驱动框架③input框架 一&#xff0c;platform1.1 platform框架&#xff08;设备树下&#xff09;1.2 platform框架&#xff08;配置设备函数&#xff09; 二&#xff0c;MISC字符驱动框架三&#xff0c;input框架 一&#xff0c;platfor…

【mysql】唯一性约束unique

文章目录 唯一性约束 1. 作用2. 关键字3. 特点4. 添加唯一约束5. 关于复合唯一约束 唯一性约束 1. 作用 用来限制某个字段/某列的值不能重复。 2. 关键字 UNIQUE3. 特点 同一个表可以有多个唯一约束。唯一约束可以是某一个列的值唯一&#xff0c;也可以多个列组合的值唯…

常见中间件漏洞:Jboss篇

CVE-2015-7501 环境搭建 cd vulhub-master/jboss/JMXInvokerServlet-deserialization docker-compose up -d 过程 访问网址&#xff0c;存在页面说明接口存在且存在反序列化漏洞 http://8.130.17.222:8080/invoker/JMXInvokerServlet 2.下载 ysoserial ⼯具进⾏漏洞利⽤…

2025年优化算法:龙卷风优化算法(Tornado optimizer with Coriolis force,TOC)

龙卷风优化算法&#xff08;Tornado optimizer with Coriolis force&#xff09;是发表在中科院二区期刊“ARTIFICIAL INTELLIGENCE REVIEW”&#xff08;IF&#xff1a;11.7&#xff09;的2025年智能优化算法 01.引言 当自然界的狂暴之力&#xff0c;化身数字世界的智慧引擎&…

3.24-3 接口测试断言

一.postman 断言 1.断言再test中 #状态码是否等于200 tests["Status code is 200"] responseCode.code 200; #断言响应时间小于200ms tests["Response time is less than 200ms"] responseTime < 200; #断言响应体包含内容 tests["Body…

DeepSeek面试——模型架构和主要创新点

本文将介绍DeepSeek的模型架构多头潜在注意力&#xff08;MLA&#xff09;技术&#xff0c;混合专家&#xff08;MoE&#xff09;架构&#xff0c; 无辅助损失负载均衡技术&#xff0c;多Token 预测&#xff08;MTP&#xff09;策略。 一、模型架构 DeepSeek-R1的基本架构沿用…

Python----计算机视觉处理(Opencv:图像亮度变换)

一、图像亮度变换 亮度调整&#xff1a;图像像素强度整体变高或者变低。 对比度调整&#xff1a;图像暗处像素强度变低&#xff0c;图像亮处像素强度变高&#xff0c;从而拉大中间某个区域范围的显示精 度。 A&#xff1a;原图 …

无人机动平衡-如何在螺旋桨上添加或移除材料

平衡无人机螺旋桨是一项精细的工作&#xff0c;直接影响飞行稳定性和组件寿命。不同的方法适用于不同的情况&#xff0c;螺旋桨的材料和尺寸以及所需调整的幅度都会影响选择的方法。 本文将深入探讨添加如胶水和胶带等材料的方法&#xff0c;以及通过打磨和修剪来移除质量的方…

基于python的租房网站-房屋出租租赁系统(python+django+vue)源码+运行步骤

该项目是基于python/django/vue开发的房屋租赁系统/租房平台&#xff0c;作为本学期的课程作业作品。欢迎大家提出宝贵建议。给师弟开发的课程作业&#xff0c;技术学习可以留言哦 功能介绍 平台采用B/S结构&#xff0c;后端采用主流的PythonDjango进行开发&#xff0c;前端采…

涨薪技术|k8s设计原理

01k8s介绍 Kubernetes 是一个可移植、可扩展的开源平台&#xff0c;用于管理容器化 工作负载和服务&#xff0c;有助于实现声明性配置和自动化。它有一个庞大、快速增长的生态系统。Kubernetes 服务、支持和工具广泛可用。Kubernetes 这个名字起源于希腊语&#xff0c;意思是舵…

基于FPGA的16QAM+帧同步系统verilog开发,包含testbench,高斯信道,误码统计,可设置SNR

目录 1.算法仿真效果 2.算法涉及理论知识概要 2.1 16QAM调制解调原理 2.2 帧同步 3.Verilog核心程序 4.完整算法代码文件获得 1.算法仿真效果 vivado2019.2仿真结果如下&#xff08;完整代码运行后无水印&#xff09;&#xff1a; 设置SNR12db 将FPGA数据导入到MATLAB显…

QuecPython 外设接口之GPIO应用指南

基础知识 了解GPIO基础知识更有益于我们使用它。 框图 GPIO&#xff08;通用输入输出&#xff09;是指一种通用的数字输入/输出接口&#xff0c;用于与外部电子元件或设备进行通信。它通常存在于微处理器、微控制器和其他嵌入式系统中。 物理电路结构如下图所示&#xff1a…