【C++设计模式 -- 单例(Singleton)模式】

C++ 单例(Singleton)模式

  • 单例模式
    • 什么是单例模式
    • 单例模式的特点
    • 为什么要使用单例模式
    • 单例模式的缺点
  • 单例模式实现
    • 懒汉式(Lazy Initialization)方式(不安全)
    • 双重检查锁(Double-Checked Locking)(线程安全)
    • 局部静态变量(线程安全)
    • 通过std::call_once创建(C++11 线程安全)

单例模式

什么是单例模式

单例模式是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点,使得程序的各个部分可以共享这个实例。单例模式的主要目的是限制一个类的实例化过程,确保在运行时只能存在一个实例,并提供一种全局访问方式。
在单例模式中,通常会将类的构造函数声明为私有的,这样外部代码无法直接实例化该类。然后,通过静态成员函数或静态成员变量来控制实例的创建和访问。如果实例不存在,就创建一个新的实例;如果实例已经存在,就返回已有的实例。

单例模式的特点

1.私有构造函数: 单例类的构造函数通常声明为私有,以防止外部直接创建实例。
2.静态成员变量或静态成员函数: 通过静态成员变量保存唯一的实例,或者通过静态成员函数提供访问实例的方法。
3.全局访问点: 单例模式提供了一个全局的访问点,允许程序的其他部分通过特定的接口访问单例实例。

为什么要使用单例模式

全局访问点: 单例模式提供了一个全局的访问点,使得程序的各个部分可以轻松地访问到相同的实例。这有助于统一管理资源或状态。
资源共享: 当一个类的实例需要被多个部分共享时,使用单例模式可以避免重复创建实例,节省系统资源。
配置管理: 单例模式常用于管理系统的配置信息,确保在整个程序运行期间只有一个配置实例,并可以被所有模块访问。
线程池、连接池等管理对象的情况: 在需要限制系统中某些资源的数量时,可以使用单例模式来管理这些资源的实例。
日志记录: 单例模式可以用于记录系统中的日志信息,确保只有一个日志实例,并提供全局的访问点,方便记录系统的运行状态。
避免重复创建开销大的对象: 当对象的创建和销毁具有较大的开销时,使用单例模式可以减少创建次数,提高性能。
虽然单例模式有其用途,但需要慎重使用,因为它可能导致全局状态的存在,使得代码的复杂性增加。在一些情况下,依赖注入、工厂模式等也是可以考虑的替代方案。

单例模式的缺点

尽管单例模式在某些情况下是有用的,但它也有一些缺点,需要在使用时考虑:
全局状态: 单例模式引入了全局状态,这可能导致代码的复杂性增加。因为任何部分都可以访问单例实例,可能导致难以追踪和理解的依赖关系。
隐藏依赖关系: 使用单例模式可能隐藏了类之间的依赖关系,因为单例实例可以在任何地方被访问。这使得代码的耦合度增加,降低了灵活性。
测试困难: 单例模式可能会使代码难以测试。因为单例实例是全局可访问的,难以在测试中用模拟对象替代实际的单例实例。
违反单一职责原则: 单例对象通常负责自己的创建、管理和业务逻辑,可能导致违反单一职责原则。这使得代码难以维护和扩展。
可能引发并发问题: 在多线程环境中,懒汉式的单例模式可能引发竞态条件问题,需要特殊处理以确保线程安全。
阻碍扩展: 单例模式的实现方式可能会阻碍程序的扩展。例如,如果需要从单例模式切换到多例模式或其他设计模式,可能需要修改大量代码。

单例模式实现

懒汉式(Lazy Initialization)方式(不安全)

实现一个简单的懒汉式单例模式。构造函数被声明为私有,确保外部无法直接创建实例。通过静态成员变量 instance 来保存唯一的实例,在 getInstance 方法中进行懒汉式的创建。示例代码如下:

#include <iostream>class Singleton {
public:// 获取单例实例的静态方法static Singleton& getInstance() {// 使用懒汉式(Lazy Initialization)方式创建单例对象// 如果实例不存在,则创建一个新的实例,否则返回现有实例if (instance == nullptr) {instance = new Singleton();}return *instance;}// 其他成员函数private:// 将构造函数、拷贝构造函数和赋值运算符声明为私有,防止外部创建多个实例Singleton() {// 构造函数私有,防止外部创建实例std::cout << "Singleton instance created." << std::endl;}Singleton(const Singleton&) = delete; // 禁用拷贝构造函数Singleton& operator=(const Singleton&) = delete; // 禁用赋值运算符// 静态成员变量,用于保存唯一实例static Singleton* instance;
};// 静态成员变量初始化为nullptr
Singleton* Singleton::instance = nullptr;int main() {// 获取单例实例Singleton& singleton1 = Singleton::getInstance();Singleton& singleton2 = Singleton::getInstance();// 输出地址,确保两次获取的是同一个实例std::cout << "Singleton 1 address: " << &singleton1 << std::endl;std::cout << "Singleton 2 address: " << &singleton2 << std::endl;return 0;
}

需要注意的是,懒汉式的单例模式在多线程环境中可能会存在竞态条件问题。因为多个线程可能同时检测到实例为nullptr,然后都尝试创建一个新的实例。为了确保线程安全,可以使用不同的线程安全机制来修复这个问题。

双重检查锁(Double-Checked Locking)(线程安全)

为了保证线程安全,可以使用std::mutex来确保在创建实例时只有一个线程能够进入临界区,从而避免了竞态条件问题。这种方式称为双重检查锁(Double-Checked Locking)机制,它在实现上相对高效,但也需要小心使用,确保在C++11及以上标准下才能正确工作。示例代码如下:

#include <iostream>
#include <mutex>class Singleton {
public:// 获取单例实例的静态方法static Singleton& getInstance() {// 使用双重检查锁(Double-Checked Locking)确保线程安全if (instance == nullptr) {std::lock_guard<std::mutex> lock(mutex);  // 使用互斥锁保护实例的创建过程if (instance == nullptr) {instance = new Singleton();}}return *instance;}// 其他成员函数private:Singleton() {std::cout << "Singleton instance created." << std::endl;}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;// 静态成员变量,用于保存唯一实例static Singleton* instance;static std::mutex mutex;  // 互斥锁,确保线程安全
};Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;int main() {// 获取单例实例Singleton& singleton1 = Singleton::getInstance();Singleton& singleton2 = Singleton::getInstance();// 输出地址,确保两次获取的是同一个实例std::cout << "Singleton 1 address: " << &singleton1 << std::endl;std::cout << "Singleton 2 address: " << &singleton2 << std::endl;return 0;
}

局部静态变量(线程安全)

除了使用双重检查锁机制,还有其他一些线程安全的单例模式实现方式。以下是其中的一种,使用局部静态变量,示例代码如下:

#include <iostream>class Singleton {
public:// 获取单例实例的静态方法static Singleton& getInstance() {static Singleton instance;  // 局部静态变量,确保线程安全return instance;}// 其他成员函数private:Singleton() {std::cout << "Singleton instance created." << std::endl;}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;
};int main() {// 获取单例实例Singleton& singleton1 = Singleton::getInstance();Singleton& singleton2 = Singleton::getInstance();// 输出地址,确保两次获取的是同一个实例std::cout << "Singleton 1 address: " << &singleton1 << std::endl;std::cout << "Singleton 2 address: " << &singleton2 << std::endl;return 0;
}

在这个实现中,将实例声明为static的局部变量,这样它只会在第一次调用getInstance时被初始化。C++11及以上标准保证了局部静态变量的线程安全性,因为初始化过程在多线程环境中是互斥的。这种方式的优点是简单且线程安全,不需要显式使用互斥锁。
需要注意的是,这种方式的局限性在于不能在运行时控制单例的创建时机,因为它是在第一次调用getInstance时自动创建的。如果需要在程序启动时就创建单例,或者需要延迟到程序的某个具体时刻再创建,就需要考虑其他实现方式。

通过std::call_once创建(C++11 线程安全)

#include <iostream>class Singleton {
public:// 获取单例实例的静态方法static Singleton& getInstance() {std::call_once(initFlag, &Singleton::initInstance);return *instance;}// 其他成员函数private:Singleton() {std::cout << "Singleton instance created." << std::endl;}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;// 初始化单例实例的函数static void initInstance() {instance = new Singleton();}// 静态成员变量,用于保存唯一实例static Singleton* instance;static std::once_flag initFlag;  // 标记是否已经初始化过
};Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::initFlag;int main() {// 获取单例实例Singleton& singleton1 = Singleton::getInstance();Singleton& singleton2 = Singleton::getInstance();// 输出地址,确保两次获取的是同一个实例std::cout << "Singleton 1 address: " << &singleton1 << std::endl;std::cout << "Singleton 2 address: " << &singleton2 << std::endl;return 0;
}

在上面的代码中,std::once_flag 用于标记初始化是否已经完成,std::call_once 保证 initInstance 函数只会在第一次调用 getInstance 时执行一次。这样可以确保线程安全地创建单例实例,同时避免了手动管理锁所带来的复杂性。

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

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

相关文章

第三百三十九回

文章目录 1. 概念介绍2. 方法与信息2.1 获取方法2.2 详细信息 3. 示例代码4. 内容总结 我们在上一章回中介绍了"蓝牙综合示例"相关的内容&#xff0c;本章回中将介绍如何获取设备信息.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章回中获…

十八、任务通知

1、前言 (1)所谓“任务通知”&#xff0c;可以反过来读"通知任务"。我们使用队列、信号量、事件组等等方法时&#xff0c;并不知道对方是谁。使用任务通知时&#xff0c;可以明确指定&#xff1a;通知哪个任务。 (2)使用队列、信号量、事件组时&#xff0c;我们都需…

2023年“中银杯”四川省职业院校技能大赛“云计算应用”赛项样题卷②

2023年“中银杯”四川省职业院校技能大赛“云计算应用”赛项&#xff08;高职组&#xff09; 样题&#xff08;第2套&#xff09; 目录 2023年“中银杯”四川省职业院校技能大赛“云计算应用”赛项&#xff08;高职组&#xff09; 样题&#xff08;第2套&#xff09; 模块…

Topics(动态路由)

Topic类型的Exchange与Direct相比&#xff0c;都是可以根据RoutingKey把消息路由到不同的队列中。只不过Topic类型Exchange可以让队列在绑定路由时可以使用通配符。 *&#xff1a;匹配不多不少刚好一个单词。 #&#xff1a;匹配一个或多个词。 举例&#xff1a; audit.#可以匹配…

2023吉利汽车大模型算法工程师面试经验

来源&#xff1a;投稿 作者&#xff1a;LSC 编辑&#xff1a;学姐 问了很多问题&#xff0c;包括实习的项目经验、各种计算机、人工智能的基础&#xff0c;时长1h30min 1.coding 给你一个整数数组 prices 和一个整数 k &#xff0c;其中 prices[i] 是某支给定的股票在第 i 天的…

解密C++中的forward<int>(a)和forward<int >(a):你真的了解它们之间的区别吗?

一文看尽C中的forward完美转发 一、前言二、深入理解forward和完美转发三、对forward<int>(a)的解析四、对forward<int &&>(a)的解析五、forward<int>(a)和forward<int &&>(a)的区别总结 一、前言 完美转发在C中具有重要性&#xff0…

Java开发一个接口提供给第三方调用

1. 环境 基于SpringBoot编写一个接口&#xff0c;提供给第三方调用。类似于我们使用阿里的语音识别功能&#xff0c;我们可以调用阿里封装好的api&#xff0c;也就是通过发送HTTP请求的方式来做语音识别。本篇文章主要记录在SpringBoot中我们是如何开发接口并让别人可以安全调…

一文掌握Java注解之@SpringBootApplication知识文集(1)

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

Spring Cloud Gateway + Nacos 实现动态路由

1、maven 依赖 主要依赖 <!-- 网关 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency>案件差不多完整主要依赖 <!--Spring boot 依赖(微服务基…

51单片机的串口驱动的代码逻辑

什么是串口通信&#xff1f; 51单片机中&#xff0c;串口通信指的是单片机和其他的机器传递数据的时候是通过一个bit一个bit的形式传输的&#xff0c;值得一提的话&#xff0c;串口通信是硬件串口。什么意思呢&#xff1f;也就是在传递比特的时候&#xff0c;不用软件来模拟比…

fanout(扇出模型)

在广播的流程下&#xff0c;消息发送的流程如下&#xff1a; 可以有多个消费者。 每个消费者有自己的queue(队列)。 每个队列都要绑定到Exchange(交换机)。 生产者发送的消息&#xff0c;只能发送到交换机&#xff0c;交换机来决定要发给哪个队列&#xff0c;生产者也无法决…

oracle物化视图

物化视图定义 视图是一个虚拟表&#xff08;也可以认为是一条语句&#xff09;&#xff0c;基于它创建时指定的查询语句返回的结果集&#xff0c;每次访问它都会导致这个查询语句被执行一次&#xff0c;为了避免每次访问都执行这个查询&#xff0c;可以将这个查询结果集存储到…

【算法题】矩阵顺时针旋转90° (js)

力扣链接&#xff1a;https://leetcode.cn/problems/rotate-matrix-lcci/description/ 本人题解&#xff1a; /*** param {number[][]} matrix* return {void} Do not return anything, modify matrix in-place instead.*/ var rotate function (matrix) {const x matrix.le…

Spring高手之路-Spring事务的传播机制(行为、特性)

目录 含义 七种事务传播机制 1.REQUIRED&#xff08;默认&#xff09; 2.REQUIRES_NEW 3.SUPPORTS 4.NOT_SUPPORTED 5.MANDATORY 6.NEVER 7.NESTED 含义 Spring事务的传播机制是指在多个事务方法相互调用时&#xff0c;如何处理这些事务的传播行为。对应七种事务传播行为…

【Git】git基础

Git 命令 git config --globle user.name ""git config --globle user.email ""git config -lgit config --globle --unset []git add []git commit -m ""]git log//当行且美观 git log --prettyoneline//以图形化和简短的方式 git log --grap…

Halcon闭运算closing

Halcon闭运算 文章目录 Halcon闭运算 闭运算的计算步骤&#xff0c;为先膨胀&#xff0c;后腐蚀。这两步操作能将看起来很接近的元素&#xff0c;如区域内部的空洞或外部孤立的点连接成一体&#xff0c;区域的外观和面积也不会有明显的改变。通俗地说&#xff0c;就是类似于“填…

Linux 权限掌控术:深入探索和用户管理

文章目录 前言1.外壳程序是什么&#xff1f;外壳程为什么存在&#xff1f;工作原理外壳程序怎么个事&#xff1f; 2. Linux权限的概念2.1 什么是权限2.2权限的本质2.3 Linux中的用户 3. 普通用户变成rootlinux中有三种人 4.Linux中文件的权限4.1文件的属性权限4.2 掌握修改权限…

TCP发送和接受数据

发送数据 public class sendmessage {public static void main (String[] args) throws IOException {//创建socket对象//在创建的同时会连接服务器,若连接不上,代码会报错Socket socketnew Socket("127.0.0.1",10086);//从连接通道中获取输出流OutputStream ossock…

2024.1.1每日一题

LeetCode每日一题 新的一年开始了&#xff0c;祝大家新年快乐&#xff0c;坚持做每日一题。 1599.经营摩天轮的最大利润 1599. 经营摩天轮的最大利润 - 力扣&#xff08;LeetCode&#xff09; 题目描述 你正在经营一座摩天轮&#xff0c;该摩天轮共有 4 个座舱 &#xff0…

DevOps系列之 JNI实现Java调用C的实现案例

JNI&#xff08;Java Native Interface&#xff09;允许Java代码与其他语言编写的代码进行交互。以下是一个简单的JNI示例&#xff0c;演示如何使用JNI在Java中调用C/C函数。 最终的目录结构如下&#xff1a; JNI&#xff08;Java Native Interface&#xff09;允许Java代码与其…