C++中的单例模式(Singleton)全面讲解与实际案例

什么是单例模式?

单例模式(Singleton)是一种创建型设计模式,旨在确保某个类在程序运行期间只有一个实例,并且提供一个全局访问点来使用该实例。这种模式在需要全局管理或共享资源的场景下非常有用。


单例模式的特点

  1. 唯一性:整个程序中,类的实例唯一存在。
  2. 全局访问点:可以通过一个静态方法访问该实例,便于全局调用。
  3. 延迟初始化:实例仅在第一次使用时创建,节省资源。

适用场景

单例模式适用于以下场景:

  1. 配置管理器:如读取和管理全局配置(数据库连接信息、文件路径等)。
  2. 日志记录器:用于记录日志,确保所有模块共享同一个日志实例。
  3. 线程池:全局统一管理线程的分配与回收。
  4. 硬件访问:如打印机驱动管理器,确保访问同一个硬件实例。

传统实现与问题

传统单例模式通常通过静态变量和手动加锁来实现。虽然可以保证实例唯一性,但这种方式在多线程环境下容易出现问题,需要额外的同步操作。

class Singleton {
private:
    static Singleton* instance;
    Singleton() {}

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
};
Singleton* Singleton::instance = nullptr;

这种实现的主要问题是:

  1. 多线程不安全:多个线程可能同时创建实例。
  2. 手动管理复杂:需要额外的锁机制来保证线程安全。

改进的现代实现

自 C++11 起,语言提供了更便捷的工具来实现单例模式,推荐使用静态局部变量来创建线程安全的单例。


实际案例:日志记录器

以下是一个实际的日志记录器实现,通过单例模式确保全局唯一性,并实现简单的日志记录功能。

代码实现

#include <iostream>
#include <fstream>
#include <mutex>
#include <string>

// 日志记录器类:单例模式实现
class Logger {
public:
    // 获取日志记录器的唯一实例
    static Logger& getInstance() {
        static Logger instance; // 静态局部变量,保证线程安全
        return instance;
    }

    // 禁止拷贝构造和赋值操作
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

    // 写日志方法
    void log(const std::string& message) {
        std::lock_guard<std::mutex> lock(logMutex); // 确保线程安全
        logFile << message << std::endl;
        std::cout << "日志已记录:" << message << std::endl; // 输出到控制台
    }

private:
    std::ofstream logFile;  // 日志文件
    std::mutex logMutex;    // 用于多线程安全的互斥锁

    // 私有构造函数
    Logger() {
        logFile.open("application.log", std::ios::app); // 追加模式打开日志文件
        if (!logFile.is_open()) {
            std::cerr << "无法打开日志文件!" << std::endl;
        } else {
            std::cout << "日志记录器初始化成功。" << std::endl;
        }
    }

    // 私有析构函数
    ~Logger() {
        if (logFile.is_open()) {
            logFile.close();
        }
    }
};

// 模拟多线程写日志
#include <thread>

void simulateLogging(const std::string& threadName) {
    Logger& logger = Logger::getInstance();
    for (int i = 1; i <= 5; ++i) {
        logger.log(threadName + " - 日志消息 " + std::to_string(i));
    }
}

int main() {
    // 主线程写日志
    Logger& logger = Logger::getInstance();
    logger.log("主线程开始运行...");

    // 创建多个线程模拟并发写日志
    std::thread thread1(simulateLogging, "线程1");
    std::thread thread2(simulateLogging, "线程2");

    // 等待线程完成
    thread1.join();
    thread2.join();

    // 主线程结束
    logger.log("主线程运行结束。");

    return 0;
}

代码解析

主要功能

  1. 单例模式的实现

    • 通过静态局部变量 static Logger instance,确保全局只有一个日志记录器实例。
    • 禁止拷贝构造和赋值操作,防止生成额外实例。
  2. 线程安全性

    • 使用 std::mutexstd::lock_guard,保证多线程同时写日志时不会出现竞争条件。
  3. 日志输出

    • 日志写入文件 application.log,并实时输出到控制台,方便调试。

运行结果

控制台输出

日志记录器初始化成功。
日志已记录:主线程开始运行...
日志已记录:线程1 - 日志消息 1
日志已记录:线程2 - 日志消息 1
日志已记录:线程1 - 日志消息 2
日志已记录:线程2 - 日志消息 2
日志已记录:线程1 - 日志消息 3
日志已记录:线程2 - 日志消息 3
日志已记录:线程1 - 日志消息 4
日志已记录:线程2 - 日志消息 4
日志已记录:线程1 - 日志消息 5
日志已记录:线程2 - 日志消息 5
日志已记录:主线程运行结束。

日志文件内容(application.log

主线程开始运行...
线程1 - 日志消息 1
线程2 - 日志消息 1
线程1 - 日志消息 2
线程2 - 日志消息 2
线程1 - 日志消息 3
线程2 - 日志消息 3
线程1 - 日志消息 4
线程2 - 日志消息 4
线程1 - 日志消息 5
线程2 - 日志消息 5
主线程运行结束。

优缺点分析

优点

  1. 全局唯一性:整个程序中,日志记录器通过 Logger::getInstance() 方法访问,确保只有一个实例。
  2. 线程安全:通过 std::mutex 确保日志操作不会因多线程引发数据竞争。
  3. 模块化与扩展性:可以轻松扩展日志记录器功能,如添加日志级别(INFO、DEBUG、ERROR)、日志格式化等。

缺点

  1. 隐藏依赖性:单例通过全局访问点可能增加模块之间的耦合性。
  2. 不易测试:单例模式的全局状态共享可能影响单元测试的独立性。

总结

日志记录器是单例模式的经典应用场景。通过单例模式,可以轻松实现全局统一管理、线程安全和资源共享。在多线程程序中,使用 C++11 的静态局部变量和互斥锁,可以确保日志记录器的安全性和稳定性。此代码是一个实际的应用案例,具有很高的实用性,可直接应用于实际开发中,同时也为单例模式的深入理解提供了很好的实践支持。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度学习-卷积神经网络CNN

案例-图像分类 网络结构: 卷积BN激活池化 数据集介绍 CIFAR-10数据集5万张训练图像、1万张测试图像、10个类别、每个类别有6k个图像&#xff0c;图像大小32323。下图列举了10个类&#xff0c;每一类随机展示了10张图片&#xff1a; 特征图计算 在卷积层和池化层结束后, 将特征…

PHP Switch 语句

<?php switch (expression) {case value1:// 代码块1break;case value2:// 代码块2break;// 更多的 case 语句default:// 如果没有匹配的值&#xff0c;输出这一行 } ?> $color 表示自己的颜色&#xff0c;需要switch循环找到对应的值。 case value : 表示对应的值&am…

Python Plotly 库使用教程

Python Plotly 库使用教程 引言 数据可视化是数据分析中至关重要的一部分&#xff0c;它能够帮助我们更直观地理解数据、发现潜在的模式和趋势。Python 提供了多种数据可视化库&#xff0c;其中 Plotly 是一个功能强大且灵活的库&#xff0c;支持交互式图表的创建。与静态图表…

ubuntu:20.04安装协议逆向工具netzob

创建容器 docker run -d --name ubuntu_env ubuntu:20.04 /bin/bash -c "while true; do sleep 1; done" 63a8f5cf5431a930671ff0e7bb2b667adf001efb05fd7261da244879d2699bec 进入容器 PS E:\src> docker exec -it ubuntu_env /bin/bash 安装常用工具 apt upda…

H3C NX30Pro刷机教程-2024-11-16

H3C NX30Pro刷机教程-2024-11-16 ref: http://www.ttcoder.cn/index.php/2024/11/03/h3c-nx30pro亲测无需分区备份 路由器-新机初始化设置路由器登录密码telnet进入路由器后台 刷机上传uboot到路由器后台在Windows环境下解压后的软件包中打开 tftpd64.exe在NX30Pro环境下通过以…

什么是嵌入式?

目录 一、什么是嵌入式 二、嵌入式系统的特点 &#xff08;一&#xff09;专用性与隐蔽性 &#xff08;二&#xff09;高可靠性与实时性 &#xff08;三&#xff09;资源固定与小型化 三、嵌入式系统的发展历史 &#xff08;一&#xff09;20 世纪 60 年代早期雏形 &am…

学习大数据DAY62 指标计算

客户需求 第一张汇总报表需要的指标 - 决策报表 汇总表 每次计算只有一天的记录 - 大 BOSS: - 全部会员数 新增会员数 - 有效会员数 有效会员占比 - 流失会员数: 倒推一年含一年无消费记录的会员 - 净增有效会员数 - 会员消费级别分类人数 (A >2000 B >1000 < …

快速上手 Vue 3 的高效组件库Element Plus

目录 前言1. 什么是组件&#xff1f;2. 安装与引入 Element Plus2.1 安装 Element Plus2.2 在 main.js 中引入 Element Plus 3. 使用 Element Plus 组件3.1 组件的基本使用3.2 控制组件状态 4. 常用组件实例解析4.1 表单与输入框4.2 表格与分页 5. 组件库的扩展性结语 前言 在…

自动驾驶车载SoC设计功能安全

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 所有人的看法和评价都是暂时的&#xff0c;只有自己的经历是伴随一生的&#xff0c;几乎所有的担忧和畏惧…

【开源免费】基于Vue和SpringBoot的私人健身与教练预约管理系统(附论文)

本文项目编号 T 618 &#xff0c;文末自助获取源码 \color{red}{T618&#xff0c;文末自助获取源码} T618&#xff0c;文末自助获取源码 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息…

【项目实战】基于 LLaMA-Factory 通过 LoRA 微调 Qwen2

【项目实战】基于 LLaMAFactory 通过 LoRA 微调 Qwen2 一、项目介绍二、环境准备1、环境准备2、安装LLaMa-Factory3、准备模型数据集3.1 模型准备3.2 数据集准备 三、微调1、启动webui2、选择参数3、训练 四、测试五、总结 一、项目介绍 LLaMA-Factory是一个由北京航空航天大学…

《Probing the 3D Awareness of Visual Foundation Models》论文解析——多视图一致性

一、论文简介 论文讨论了大规模预训练产生的视觉基础模型在处理任意图像时的强大能力&#xff0c;这些模型不仅能够完成训练任务&#xff0c;其中间表示还对其他视觉任务&#xff08;如检测和分割&#xff09;有用。研究者们提出了一个问题&#xff1a;这些模型是否能够表示物体…

C++ | Leetcode C++题解之第565题数组嵌套

题目&#xff1a; 题解&#xff1a; class Solution { public:int arrayNesting(vector<int> &nums) {int ans 0, n nums.size();for (int i 0; i < n; i) {int cnt 0;while (nums[i] < n) {int num nums[i];nums[i] n;i num;cnt;}ans max(ans, cnt);…

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-04

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-04 目录 文章目录 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-04目录1. Alopex: A Computational Framework for Enabling On-Device Function Calls with LLMs摘要&#xff1a;研究背景&…

智能运维:提升效率与响应速度的关键能力

在当今这个信息化高速发展的时代&#xff0c;运维工作的重要性日益凸显。一个高效、智能的运维系统不仅能够确保企业IT环境的稳定运行&#xff0c;还能在出现问题时迅速响应&#xff0c;最小化业务中断的影响。本文将深入探讨现代运维系统应具备的关键能力&#xff0c;包括告警…

Linux 下网络套接字(Socket) 与udp和tcp 相关接口

文章目录 1. socket常见API2 sockaddr结构体及其子类1. sockaddr结构体定义&#xff08;基类&#xff09;2. 子类 sockaddr_in结构体用于(IPv4)3 子类 sockaddr_un(Unix域套接字)4. 总结画出其结构体 3.实现一个简单的tcp Echo 服务器和客户端(cpp&#xff09;3.1 客户端3.2 服…

IPv6基础知识

IPv6是由IEIF提出的互聯網協議第六版&#xff0c;用來替代IPv4的下一代協議&#xff0c;它的提出不僅解決了網絡地址資源匱乏問題&#xff0c;也解決了多種接入設備接入互聯網的障礙。IPv6的地址長度為128位&#xff0c;可支持340多萬億個地址。如下圖&#xff0c;3ffe:1900:fe…

24首届数证杯(流量分析部分)

目录 流量分析 流量分析 1、分析网络流量包检材&#xff0c;写出抓取该流量包时所花费的秒数?(填写数字&#xff0c;答案格式:10) 3504相加即可 2、分析网络流量包检材&#xff0c;抓取该流量包时使用计算机操作系统的build版本是多少? 23F793、分析网络流量包检材&#x…

Linux(CentOS)安装达梦数据库 dm8

CentOS版本&#xff1a;CentOS 7&#xff0c;查看操作系统版本信息&#xff0c;请查阅 查看Linux内核版本信息 达梦数据库版本&#xff1a;dm8 一、获取 dm8 安装文件 1、下载安装文件 打开达梦官网&#xff1a;https://www.dameng.com/ 下载的文件 解压后的文件 2、上传安…

vue-i18n下载完报错

解决方法&#xff1a; 这是i18n版本太高了&#xff0c;与当前VUE版本不谦容&#xff1b; 查看版本&#xff1a;npm view vue-i18n versions 选择其中一个低版本&#xff0c;不要太低的 npm install vue-i18n7.3.22.可以删掉依赖包重新下载试试 报错类似如下&#xff1a; 1…/…