当前位置: 首页 > news >正文

《C++设计模式》单例模式

文章目录

  • 1、简介
  • 2、单例模式的种类
    • 2.1 饿汉式单例模式:
    • 2.2 懒汉式单例模式:
  • 3、单例模式的具体介绍
    • 3.1、饿汉式
      • 3.1.1、代码示例
      • 3.1.2、组成部分
      • 3.1.3、优缺点
      • 3.1.4、应用场景
    • 3.2、懒汉式
      • 3.2.1、代码示例
      • 3.2.2、组成部分
      • 3.2.3、优缺点
      • 3.2.4、应用场景
  • 4、面试常问问题
  • 5、总结
  • 6、参考文章

1、简介

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。这个模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。
以下是单例模式的一些关键点
(1)私有构造方法:为了防止外部代码通过new关键字创建类的多个实例,单例模式的构造方法是私有的。
(2)静态变量:类的唯一实例通常存储在一个静态变量中,这样它可以被类的所有实例共享和访问。
(3)公共静态方法:为了提供对唯一实例的全局访问,单例模式通常包含一个公共的静态方法,该方法在需要时返回类的唯一实例。如果实例尚不存在,该方法会创建它;如果实例已经存在,则直接返回该实例。
(4)线程安全:在多线程环境中,需要确保单例模式的实现是线程安全的,以防止多个线程同时创建类的多个实例。这通常通过同步机制来实现。

2、单例模式的种类

2.1 饿汉式单例模式:

(1)在类加载时就创建实例。
(2)线程安全,因为实例在类加载时就已经存在,不存在多线程竞争问题。
(3)可能导致内存浪费,因为即使从未使用过该实例,它仍然会被创建。
(4)实现简单,通常通过静态变量和静态代码块来完成。

2.2 懒汉式单例模式:

(1)在第一次使用时才创建实例。
(2)非线程安全,因为多个线程可能同时进入创建实例的代码块,导致创建多个实例(除非添加额外的同步机制)。
(3)节省内存资源,因为实例只在需要时才创建。
(4)实现相对复杂,需要处理线程安全问题,通常通过同步方法或双重检查锁定来实现。

3、单例模式的具体介绍

3.1、饿汉式

3.1.1、代码示例

#include <iostream>class Singleton {
public:// 禁用拷贝构造函数和赋值运算符,确保单例的唯一性Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;// 提供一个公共的静态方法来获取单例对象static Singleton& getInstance() {static Singleton instance; // 静态局部变量,只在第一次调用时构造return instance;}// 一个示例方法,用于展示单例对象的使用void doSomething() {std::cout << "Doing something in Singleton instance!" << std::endl;}private:// 私有构造函数,防止外部直接创建实例Singleton() {std::cout << "Singleton instance created!" << std::endl;}// 禁用析构函数,防止外部调用delete(实际上在程序结束时会自动调用)// 注意:对于饿汉式单例,析构函数通常不需要特别处理,因为实例会在程序结束时自动销毁// 但为了完整性,这里仍然声明为私有,并添加注释~Singleton() = default;
};int main() {// 获取单例对象并调用其方法Singleton& singleton = Singleton::getInstance();singleton.doSomething();// 尝试获取同一个单例对象(实际上仍然是同一个实例)Singleton& anotherSingleton = Singleton::getInstance();anotherSingleton.doSomething();// 由于拷贝构造函数和赋值运算符被禁用,以下代码会导致编译错误// Singleton copySingleton = Singleton::getInstance(); // 错误:拷贝构造函数被删除// Singleton assignSingleton;// assignSingleton = Singleton::getInstance(); // 错误:赋值运算符被删除return 0;
}

运行结果:

Singleton instance created!
Doing something in Singleton instance!
Doing something in Singleton instance!

3.1.2、组成部分

(1)私有静态成员变量:
这是一个静态的类成员变量,用于存储单例实例。由于它是私有的,所以外部类不能直接访问或修改它。
(2)私有构造函数:
类的构造函数被声明为私有,以防止外部类通过new关键字创建新的实例。这是实现单例模式的关键之一。
(3)公共静态方法:
提供一个公共的静态方法(通常是getInstance),用于返回单例实例。这个方法检查静态成员变量是否已经持有实例,如果还没有,则创建一个新的实例并返回;如果已经存在,则直接返回该实例。
(4)静态初始化块(可选):
在某些情况下,单例实例的初始化可能需要执行一些复杂的逻辑,这时可以使用静态初始化块来完成。然而,在饿汉模式中,由于实例在类加载时就已经被创建,所以通常不需要静态初始化块。

3.1.3、优缺点

(1) 优点:线程安全,实现简单。
(2) 缺点:可能导致内存浪费,因为即使从未使用过该实例,它仍然会被创建。

3.1.4、应用场景

(1)线程安全需求高:由于饿汉模式在类加载时就创建了实例,所以它是线程安全的,无需额外的同步机制。这在多线程环境中尤其有用,可以避免竞争条件和潜在的线程安全问题。
(2)实例创建开销小:如果实例的创建开销很小,或者实例的创建和初始化过程不会消耗太多资源,那么饿汉模式是一个很好的选择。因为即使实例在类加载时被创建,也不会对系统性能产生显著影响。
(3)实例需要提前准备:在某些情况下,实例需要在类加载时就准备好,以便在后续的代码执行过程中随时使用。例如,某些配置信息或资源需要在应用程序启动时就被加载和初始化。

3.2、懒汉式

3.2.1、代码示例

#include <iostream>
#include <mutex>
#include <memory>class Singleton {
public:// 禁用拷贝构造函数和赋值运算符Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;// 获取单例实例的静态方法static Singleton* getInstance() {// 使用双重检查锁定(Double-Checked Locking)来确保线程安全和性能if (instance_ == nullptr) {std::lock_guard<std::mutex> lock(mutex_);if (instance_ == nullptr) {instance_ = new Singleton();}}return instance_;}// 示例方法void doSomething() {std::cout << "Singleton instance is doing something!" << std::endl;}// 析构函数,用于清理资源~Singleton() {std::cout << "Singleton instance is being destroyed!" << std::endl;}private:// 私有构造函数,防止外部创建实例Singleton() {std::cout << "Singleton instance is being created!" << std::endl;}// 静态成员变量,保存单例实例static Singleton* instance_;// 静态互斥锁,用于线程同步static std::mutex mutex_;
};// 初始化静态成员变量
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;int main() {// 获取单例实例并调用示例方法Singleton* singleton1 = Singleton::getInstance();singleton1->doSomething();// 再次获取单例实例(应该是同一个实例)Singleton* singleton2 = Singleton::getInstance();singleton2->doSomething();// 注意:main函数结束时,单例实例的析构函数会被调用return 0;
}

3.2.2、组成部分

(1)私有的构造函数
懒汉式单例模式通过将构造函数私有化,防止外部通过new关键字直接创建多个实例。这是实现单例模式的关键一步,因为如果没有私有化构造函数,外部代码就可以随意创建类的实例,从而破坏单例模式的规则。
(2) 私有的静态实例变量
懒汉式单例模式使用一个私有的静态实例变量来存储唯一的实例。这个变量在类加载时不会被初始化,而是在第一次调用获取实例的方法时才会被创建。由于该变量是静态的,因此它只会在类的整个生命周期中存在一个实例。
(3) 公共的静态方法
懒汉式单例模式通过提供一个公共的静态方法来获取唯一的实例。这个方法首先会检查私有的静态实例变量是否为null,如果是,则创建一个新的实例并将其赋值给该变量。如果实例已经存在,则直接返回该实例。
(4)线程安全机制(可选)
在多线程环境下,懒汉式单例模式可能会出现多个线程同时创建实例的情况。为了解决这个问题,可以在获取实例的方法中添加同步机制,如使用synchronized关键字或java.util.concurrent包中的锁机制来确保只有一个线程能够创建实例。

3.2.3、优缺点

(1) 优点:节省内存资源,因为实例只在需要时才创建。
(2) 缺点:非线程安全,因为多个线程可能同时进入创建实例的代码块,导致创建多个实例。需要额外的同步机制来保证线程安全。

3.2.4、应用场景

(1)延迟加载:如果实例的创建开销较大,或者实例的创建和初始化过程会消耗较多资源,那么懒汉模式可以通过延迟加载来节省资源。只有在实际需要使用时,才会创建和初始化实例。
(2)节省内存:在某些情况下,如果实例在类加载时并不需要使用,那么懒汉模式可以避免不必要的内存占用。只有在需要时,才会分配内存和创建实例。
(3)实例创建依赖条件:如果实例的创建依赖于某些条件或参数,并且这些条件或参数在类加载时并不确定,那么懒汉模式可以根据实际情况来创建实例。这提供了更大的灵活性和适应性。

4、面试常问问题

对于C++的单例模式,面试高频问题主要集中在单例模式的基本概念、应用场景、实现方式及其相关细节上。以下是一些可能的面试问题及简要解答:
一、单例模式的基本概念
(1)什么是C++中的单例模式?
单例模式是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。在C++中,这通常通过私有化构造函数、拷贝构造函数和赋值运算符,并提供一个静态的公共方法来获取实例来实现。
(2)单例模式的主要目的是什么?
单例模式的主要目的是确保整个进程中,只有一个类的实例,并且提供一个统一的访问接口。这通常用于需要全局访问并共享资源的场景,如日志记录器、配置管理器和数据库连接器等。
二、单例模式的应用场景
(1)请列举一些C++中单例模式的应用场景?
日志记录器:确保整个应用程序中只有一个日志记录器实例,以便集中管理日志输出。
配置管理器:用于读取和管理应用程序的配置信息,确保配置信息的一致性和全局可访问性。
数据库连接器:在需要连接数据库时,确保只有一个数据库连接器实例,以减少资源消耗和提高性能。
三、单例模式的实现方式
(1)C++中实现单例模式有哪些常见的方法?
饿汉式:在类加载时就初始化实例,线程安全但可能会浪费空间。
懒汉式:在第一次调用时才初始化实例,实现了懒加载,但需要注意线程安全问题。可以通过加锁或使用双重检查锁来确保线程安全。
静态内部类实现:利用C++11中的局部静态变量特性,在第一次调用时初始化实例,线程安全且效率高。
(2)什么是双重检查锁(DCL)?它在C++单例模式中的作用是什么?
双重检查锁是一种优化技术,用于在懒汉式单例模式中确保线程安全的同时提高效率。它首先检查实例是否已经存在,如果不存在则加锁,然后再次检查实例是否存在(以避免在加锁期间其他线程已经创建了实例),最后创建实例。这样可以减少不必要的加锁操作,提高性能。
(3)为什么C++11中的局部静态变量可以用于实现线程安全的单例模式?
C++11标准保证了局部静态变量的初始化是线程安全的。因此,在静态方法中利用局部静态变量来初始化单例实例,可以确保在多线程环境中只有一个线程能够创建实例,而其他线程将直接获取到已经创建的实例。
四、单例模式的进阶问题
(1)如何防止C++中的单例模式被反射攻击?
在C++中,由于不存在像Java那样的反射机制,因此通常不需要特别考虑防止反射攻击的问题。但是,如果使用了某些支持反射的库或框架,则需要通过适当的措施来防止反射攻击,如私有化构造函数和析构函数等。
(2)如何确保C++中的单例模式在序列化和反序列化过程中保持唯一性?
在C++中,如果单例类需要被序列化,则需要实现自定义的序列化逻辑。在反序列化过程中,可以检查是否已经存在实例,如果存在则直接返回该实例而不是创建新实例。这通常需要在序列化时保存一些额外的信息来标识实例的唯一性。
(3)C++中的单例模式是否可以被删除或重置?
在大多数情况下,单例模式不需要被删除或重置。因为单例实例通常在整个应用程序的生命周期内都存在,并且被多个模块或组件共享。然而,在某些特殊情况下(如单元测试或应用程序重置等),可能需要提供一种机制来删除或重置单例实例。这可以通过在单例类中添加一个专用的销毁函数来实现,但需要注意线程安全和资源释放等问题。

5、总结

单例模式是一种简单而有效的设计模式,但在使用时需要注意其优缺点和注意事项,以确保其正确性和高效性。

6、参考文章

本文章由文心一言搜索的来的。

相关文章:

《C++设计模式》单例模式

文章目录 1、简介2、单例模式的种类2.1 饿汉式单例模式&#xff1a;2.2 懒汉式单例模式&#xff1a; 3、单例模式的具体介绍3.1、饿汉式3.1.1、代码示例3.1.2、组成部分3.1.3、优缺点3.1.4、应用场景 3.2、懒汉式3.2.1、代码示例3.2.2、组成部分3.2.3、优缺点3.2.4、应用场景 4…...

mapbox进阶,添加路径规划控件

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️MapboxDirections 控件二、🍀添加路径规划控件1. ☘️实现思路2. ☘️…...

【论文阅读笔记】SCI算法与代码 | 低照度图像增强 | 2022.4.21

目录 一 SCI 1 SCI网络结构 核心代码&#xff08;model.py&#xff09; 2 SCI损失函数 核心代码&#xff08;loss.py&#xff09; 3 实验 二 SCI效果 1 下载代码 2 运行 一 SCI &#x1f49c;论文题目&#xff1a;Toward Fast, Flexible, and Robust Low-Light Image …...

RAG实战:本地部署ragflow+ollama(linux)

1.部署ragflow 1.1安装配置docker 因为ragflow需要诸如elasticsearch、mysql、redis等一系列三方依赖&#xff0c;所以用docker是最简便的方法。 docker安装可参考Linux安装Docker完整教程&#xff0c;安装后修改docker配置如下&#xff1a; vim /etc/docker/daemon.json {…...

前路漫漫,曙光在望 !

起始 从20年大一开始写作至今&#xff0c;转眼五年时光已经过去了&#xff0c;最开始在CSDN这个平台写博客也只是因为一次机缘巧合情况下得知写博客可以获取奖赏&#xff0c;所以那个时期开始疯狂在CSDN发文记录自己编程学习过程&#xff0c;但是至今也未从写作中获利一分哈…...

特征工程-特征预处理

1.7 特征工程-特征预处理 学习目标 目标 了解什么是特征预处理知道归一化和标准化的原理及区别 1 什么是特征预处理 1.1 特征预处理定义 scikit-learn的解释 provides several common utility functions and transformer classes to change raw feature vectors into a represe…...

代码随想录算法训练营day22

代码随想录算法训练营 —day22 文章目录 代码随想录算法训练营前言回溯算法理论基础回溯法解决的问题回溯法模板 一、77. 组合二、216. 组合总和 III三、17. 电话号码的字母组合总结 前言 今天是算法营的第22天&#xff0c;希望自己能够坚持下来&#xff01; 今日任务&#x…...

2024秋语法分析作业-B(满分25分)

特别注意&#xff1a;第17条产生式改为 17) Stmt → while ( Cond ) Stmt 【问题描述】 本次作业只测试一个含简单变量声明、赋值语句、输出语句、if语句和while语句的文法&#xff1a; 0) CompUnit → Block 1) Block → { BlockItemList } 2) BlockItemList → BlockItem…...

Python爬虫入门(1)

在互联网时代&#xff0c;数据成为了最宝贵的资源之一。Python作为一种功能强大的编程语言&#xff0c;因其简洁的语法和丰富的库支持&#xff0c;成为了编写网络爬虫的首选。本文将带你入门Python爬虫技术&#xff0c;让你能够从互联网上自动获取数据。 什么是爬虫&#xff1…...

鸿蒙1.2:第一个应用

1、create Project&#xff0c;选择Empty Activity 2、配置项目 project name 为项目名称&#xff0c;建议使用驼峰型命名 Bundle name 为项目包名 Save location 为保存位置 Module name 为模块名称&#xff0c;即运行时需要选择的模块名称&#xff0c;见下图 查看模块名称&…...

2024年常用工具

作为本年度高频使用工具&#xff0c;手机端也好&#xff0c;桌面端也好&#xff0c;筛选出来9款产品&#xff0c;这里也分享给关注我的小伙伴 &#xff0c;希望对你有些帮助&#xff0c;如果你更好的产品推荐&#xff0c;欢迎留言给我。 即刻 产品经理的聚集地&#xff0c;“让…...

【蓝桥杯】走迷宫

题目&#xff1a; 解题思路&#xff1a; 简单的广度优先算法&#xff08;BFS&#xff09; BFS 的特性 按层次遍历&#xff1a;BFS 按照节点的距离&#xff08;边的数量&#xff09;来逐层访问节点。保证最短路径&#xff1a;对于无权图&#xff08;所有边权重相同&#xff0…...

【pyqt】(三)designer

designer ui设计 在学习后续的代码之前&#xff0c;我们可以先学习一下designer这款工具&#xff0c;在安装软件的时候我们有提到过&#xff0c;其具体位置在虚拟环境根目录下的\Lib\site-packages\PySide6文件夹中。对于新手而言&#xff0c;使用这种可视化的工具可以帮助我们…...

【Go学习】-01-3-函数 结构体 接口 IO

【Go学习】-01-3-函数 结构体 接口 IO 1 函数1.1 函数概述1.1.1 函数做为参数1.1.2 函数返回值 1.2 参数1.3 匿名函数1.4 闭包1.5 延迟调用1.6 异常处理 2 结构体2.1 实例化2.2 匿名结构体2.3 匿名字段 3 类方法3.1 接收器3.2 类方法练习&#xff1a;二维矢量模拟玩家移动3.3 给…...

昆仑万维大数据面试题及参考答案

请介绍一下 Flume 组件。 Flume 是一个分布式、可靠、高可用的海量日志采集、聚合和传输的系统。 从架构层面来看,它主要包含以下几个关键部分。首先是 Source,它是数据的收集端,能够接收多种不同来源的数据。比如,它可以从各种服务器的日志文件中读取数据,像 Web 服务器产…...

20250103在Ubuntu20.04.5的Android Studio 2024.2.1.12中跑通Hello World

20250103在Ubuntu20.04.5的Android Studio 2024.2.1.12中跑通Hello World 2025/1/3 14:06 百度&#xff1a;android studio helloworld android studio hello world kotlin helloword kotlin 串口 no run configurations added android studio no run configurations added 1、…...

Hack The Box-Starting Point系列Three

答案 How many TCP ports are open?&#xff08;靶机开了几个TCP端口&#xff09; 2What is the domain of the email address provided in the “Contact” section of the website?&#xff08;网站的“CONTACT”部分提供的电子邮件地址的域是什么&#xff1f;&#xff09…...

【Python其他生成随机字符串的方法】

在Python中&#xff0c;除了之前提到的方法外&#xff0c;确实还存在其他几种生成随机字符串的途径。以下是对这些方法的详细归纳&#xff1a; 方法一&#xff1a;使用random.randint结合ASCII码生成 你可以利用random.randint函数生成指定范围内的随机整数&#xff0c;这些整…...

redis7基础篇2 redis的主从模式1

目录 一 主从模式 1.1 主从复制的作用 1.2 配置常用命令 1.3 主从复制常见问题 1.4 主从复制的缺点 1.5 redis主从复制原理 二 redis主从复制的搭建流程 2.1 注意事项 2.2 redis的主从复制架构图 2.3 以6379.conf配置文件配置为例 2.4 以6380.conf配置文件配置为例 …...

Springboot - Web

Spring Boot 是一个用于简化 Spring 应用程序配置和部署的框架。它提供了一种快速开发的方式&#xff0c;通过默认配置、自动化配置等特性&#xff0c;使得开发者能够更快捷地构建和部署基于 Spring 的应用。 Spring Boot Web 是 Spring Boot 的一个子模块&#xff0c;它专注于…...

【C】​动态内存管理

所谓动态内存管理&#xff0c;就是使得内存可以动态开辟&#xff0c;想使用的时候就开辟空间&#xff0c;使用完之后可以销毁&#xff0c;将内存的使用权还给操作系统&#xff0c;那么动态开辟内存有什么用呢&#xff1f; 假设有这么一种情况&#xff0c;你在一家公司中工作&am…...

lec5-传输层原理与技术

lec5-传输层原理与技术 1. 传输层概述 1.1. 关键职责 flow control&#xff0c;流量控制reliability&#xff0c;可靠性 1.2. TCP与UDP对比 面向连接 / 不能连接对数据校验 / 不校验数据丢失重传 / 不会重传有确认机制 / 没有确认滑动窗口流量控制 / 不会流量控制 1.3. 关…...

【C语言】_指针运算

目录 1. 指针-整数 2. 指针-指针 2.1 指针-指针含义 2.2 指针-指针运算应用&#xff1a;实现my_strlen函数 3. 指针的关系运算&#xff08;大小比较&#xff09; 1. 指针-整数 联系关于指针变量类型关于指针类型和指针-整数相关知识&#xff1a; 原文链接如下&#xff1…...

“AI智慧教学系统:开启个性化教育新时代

大家好&#xff0c;我是老王&#xff0c;一个在产品圈摸爬滚打多年的资深产品经理。今天&#xff0c;我想和大家聊聊一个最近特别火的概念——AI智慧教学系统。这东西听起来好像很高大上&#xff0c;但其实和我们每个人都息息相关&#xff0c;因为它关系到我们下一代的教育。 一…...

商用车自动驾驶,迎来大规模量产「临界点」?

商用车自动驾驶&#xff0c;正迎来新的行业拐点。 今年初&#xff0c;交通部公开发布AEB系统运营车辆标配征求意见稿&#xff0c;首次将法规限制条件全面放开&#xff0c;有望推动商用车AEB全面标配&#xff0c;为开放场景的商用车智能驾驶市场加了一把火。 另外&#xff0c;…...

CSS 学习之正确看待 CSS 世界里的 margin 合并

一、什么是 margin 合并 块级元素的上外边距(margin-top)与下外边距(margin-bottom)有时会合并为单个外边距&#xff0c;这样的现象称为“margin 合并”。从此定义上&#xff0c;我们可以捕获两点重要的信息。 块级元素&#xff0c;但不包括浮动和绝对定位元素&#xff0c;尽…...

杰发科技——使用ATCLinkTool解除读保护

0. 原因 在jlink供电电压不稳定的情况下&#xff0c;概率性出现读保护问题&#xff0c;量产时候可以通过离线烧录工具避免。代码中开了读保护&#xff0c;但是没有通过can/uart/lin/gpio控制等方式进行关闭&#xff0c;导致无法关闭读保护。杰发所有芯片都可以用本方式解除读保…...

uni-app深度解码:跨平台APP开发的核心引擎与创新实践

在当今数字化浪潮中&#xff0c;移动应用市场呈现出爆炸式增长。为了满足不同用户群体在不同操作系统上的需求&#xff0c;跨平台 APP 开发成为众多开发者的首选策略。uni-app 作为一款领先的跨平台开发框架&#xff0c;以其独特的优势和创新的实践在众多同类产品中脱颖而出。它…...

unity团结云下载项目

今天开plastic scm发现它云服务好像停了哈&#xff0c;在hub里下载云端项目也不会出现在项目列表里&#xff0c;之前也有发邮件说让提前迁移到团结云。打开云仓库会弹这个&#xff0c;大概就是plastic scm无法解析域名地址吧 研究了一下团结云咋使&#xff0c;官方手册看半天也…...

Jmeter进阶篇(31)解决java.net.BindException: Address already in use: connect报错

📚前言 近期雪雪妹妹在使用Jmeter执行压测的时候,发现了一个非常让她头疼的问题,她使用20并发跑,正确率可以达到100%,但是一旦使用200并发,就会出现大量的报错,报错内容如下: java.net.BindException: Address already in use: connectat java.net.DualStackPlainSo…...