单例模式(C++)
定义
保证一个类仅有一个实例,并提供一个该实例的全局访问点。
应用场景
- 在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。
- 如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?
- 这应该是类设计者的责任,而不是使用者的责任。
结构
代码示例
普通懒汉式(线程不安全)
多线程情况下线程不安全
//Singleton.h
/****************************************************/
#ifndef SINGLETON_H
#define SINGLETON_H/// 普通懒汉式实现 -- 线程不安全 //
#include <iostream> // std::cout
#include <mutex> // std::mutex
#include <pthread.h> // pthread_create
class SingleInstance
{public:// 获取单例对象static SingleInstance *GetInstance();// 释放单例,进程退出时调用static void deleteInstance();// 打印单例地址void Print();private:// 将其构造和析构成为私有的, 禁止外部构造和析构SingleInstance();~SingleInstance();// 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值SingleInstance(const SingleInstance &signal);const SingleInstance &operator=(const SingleInstance &signal);private:// 唯一单例对象指针static SingleInstance *m_SingleInstance;
};//初始化静态成员变量
SingleInstance *SingleInstance::m_SingleInstance = NULL;SingleInstance* SingleInstance::GetInstance()
{if (m_SingleInstance == NULL){m_SingleInstance = new (std::nothrow) SingleInstance; // 没有加锁是线程不安全的,当线程并发时会创建多个实例}return m_SingleInstance;
}void SingleInstance::deleteInstance()
{if (m_SingleInstance){delete m_SingleInstance;m_SingleInstance = NULL;}
}void SingleInstance::Print()
{std::cout << "我的实例内存地址是:" << this << std::endl;
}SingleInstance::SingleInstance()
{std::cout << "构造函数" << std::endl;
}SingleInstance::~SingleInstance()
{std::cout << "析构函数" << std::endl;
}
/// 普通懒汉式实现 -- 线程不安全 //// 线程函数
void *PrintHello(void *threadid)
{// 主线程与子线程分离,两者相互不干涉,子线程结束同时子线程的资源自动回收pthread_detach(pthread_self());// 对传入的参数进行强制类型转换,由无类型指针变为整形数指针,然后再读取int tid = *((int *)threadid);std::cout << "Hi, 我是线程 ID:[" << tid << "]" << std::endl;// 打印实例地址SingleInstance::GetInstance()->Print();pthread_exit(NULL);
}#endif
//test.cpp
/****************************************************/
#include "Singleton.h"
#include <pthread.h> // pthread_create
#define NUM_THREADS 5 // 线程个数
int main()
{pthread_t threads[NUM_THREADS] = {0};int indexes[NUM_THREADS] = {0}; // 用数组来保存i的值int ret = 0;int i = 0;std::cout << "main() : 开始 ... " << std::endl;for (i = 0; i < NUM_THREADS; i++){std::cout << "main() : 创建线程:[" << i << "]" << std::endl;indexes[i] = i; //先保存i的值// 传入的时候必须强制转换为void* 类型,即无类型指针ret = pthread_create(&threads[i], NULL, PrintHello, (void *)&(indexes[i]));if (ret){std::cout << "Error:无法创建线程," << ret << std::endl;exit(-1);}}// 手动释放单实例的资源SingleInstance::deleteInstance();std::cout << "main() : 结束! " << std::endl;return 0;
}
加锁懒汉式(线程安全)
//Singleton.h
/****************************************************/
#ifndef SINGLETON_H
#define SINGLETON_H
#include <pthread.h> // pthread_create
#include <iostream> // std::cout
#include <mutex> // std::mutex
/// 加锁的懒汉式实现 //
class SingleInstance
{public:// 获取单实例对象static SingleInstance *&GetInstance();//释放单实例,进程退出时调用static void deleteInstance();// 打印实例地址void Print();private:// 将其构造和析构成为私有的, 禁止外部构造和析构SingleInstance();~SingleInstance();// 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值SingleInstance(const SingleInstance &signal);const SingleInstance &operator=(const SingleInstance &signal);private:// 唯一单实例对象指针static SingleInstance *m_SingleInstance;static std::mutex m_Mutex;
};//初始化静态成员变量
SingleInstance *SingleInstance::m_SingleInstance = NULL;
std::mutex SingleInstance::m_Mutex;SingleInstance *&SingleInstance::GetInstance()
{// 这里使用了两个 if判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,// 避免每次调用 GetInstance的方法都加锁,锁的开销毕竟还是有点大的。if (m_SingleInstance == NULL) {std::unique_lock<std::mutex> lock(m_Mutex); // 加锁if (m_SingleInstance == NULL){m_SingleInstance = new (std::nothrow) SingleInstance;}}return m_SingleInstance;
}void SingleInstance::deleteInstance()
{std::unique_lock<std::mutex> lock(m_Mutex); // 加锁if (m_SingleInstance){delete m_SingleInstance;m_SingleInstance = NULL;}
}void SingleInstance::Print()
{std::cout << "我的实例内存地址是:" << this << std::endl;
}SingleInstance::SingleInstance()
{std::cout << "构造函数" << std::endl;
}SingleInstance::~SingleInstance()
{std::cout << "析构函数" << std::endl;
}
/// 加锁的懒汉式实现 //#endif
静态局部变量的懒汉单例(C++11线程安全)
//Singleton.h
/****************************************************/
#ifndef SINGLETON_H
#define SINGLETON_H
#include <pthread.h> // pthread_create
#include <iostream> // std::cout
#include <mutex> // std::mutex
/// 内部静态变量的懒汉实现 //
class Single
{public:// 获取单实例对象static Single &GetInstance();// 打印实例地址void Print();private:// 禁止外部构造Single();// 禁止外部析构~Single();// 禁止外部复制构造Single(const Single &signal);// 禁止外部赋值操作const Single &operator=(const Single &signal);
};Single &Single::GetInstance()
{// 局部静态特性的方式实现单实例static Single signal;return signal;
}void Single::Print()
{std::cout << "我的实例内存地址是:" << this << std::endl;
}Single::Single()
{std::cout << "构造函数" << std::endl;
}Single::~Single()
{std::cout << "析构函数" << std::endl;
}
/// 内部静态变量的懒汉实现 //#endif
饿汉式(本身就线程安全)
//Singleton.h
/****************************************************/
#ifndef SINGLETON_H
#define SINGLETON_H
#include <pthread.h> // pthread_create
#include <iostream> // std::cout
#include <mutex> // std::mutex
// 饿汉实现 /
class Singleton
{
public:// 获取单实例static Singleton* GetInstance();// 释放单实例,进程退出时调用static void deleteInstance();// 打印实例地址void Print();private:// 将其构造和析构成为私有的, 禁止外部构造和析构Singleton();~Singleton();// 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值Singleton(const Singleton &signal);const Singleton &operator=(const Singleton &signal);private:// 唯一单实例对象指针static Singleton *g_pSingleton;
};// 代码一运行就初始化创建实例 ,本身就线程安全
Singleton* Singleton::g_pSingleton = new (std::nothrow) Singleton;Singleton* Singleton::GetInstance()
{return g_pSingleton;
}void Singleton::deleteInstance()
{if (g_pSingleton){delete g_pSingleton;g_pSingleton = NULL;}
}void Singleton::Print()
{std::cout << "我的实例内存地址是:" << this << std::endl;
}Singleton::Singleton()
{std::cout << "构造函数" << std::endl;
}Singleton::~Singleton()
{std::cout << "析构函数" << std::endl;
}
// 饿汉实现 /#endif
代码参考:C++ 线程安全的单例模式总结
我这里运行结果总是有点不尽人意,不知道是怎么回事,所以就没放运行结果,推荐看原文。
要点总结
- Singleton模式中的实例构造器可以设置为protected以允许子类派生。
- Singleton模式一般不要支持拷贝构造函数和Clone接口,因为这有可能导致多个对象实例,与Singleton模式的初衷违背。
- 如何实现多线程环境下安全的Singleton?注意对双检查锁的正确实现。
相关文章:

单例模式(C++)
定义 保证一个类仅有一个实例,并提供一个该实例的全局访问点。 应用场景 在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。如何绕过常规的构造器,提供一种…...

LeetCode 热题 100 JavaScript--234. 回文链表
function ListNode(val, next) {this.val val undefined ? 0 : val;this.next next undefined ? null : next; }var isPalindrome function (head) {if (!head || !head.next) {return true; }// 使用快慢指针法找到链表的中间节点let slow head;let fast head;while …...

Redis 6.5 服务端开启多线程源码
redis支持开启多线程,只有从socket到读取缓冲区和从输出缓冲区到socket这两段过程是多线程,而命令的执行还是单线程,并且是由主线程执行 借鉴:【Redis】事件驱动框架源码分析(多线程) 一、main启动时初始化…...
嵌入式面试笔试刷题(day6)
文章目录 前言一、进程和线程的区别二、共享内存的原理三、中断有传参和返回值吗四、串口数据帧格式五、进程通信有几种,哪几种需要借助内核1.方式2.需要借助内核的 六、flash有哪几种类型七、指针的本质是什么八、指针和数组的区别九、使用宏定义交换变量不能使用中…...

24考研数据结构-第五章:树与二叉树
目录 第五章:树5.1树的基本概念5.1.1树的定义5.1.2 基本术语5.1.3 树的性质 5.2二叉树的概念5.2.1 二叉树的定义与特性5.2.2 几种特殊的二叉树5.2.3 二叉树的性质5.2.4 完全二叉树的性质5.2.5 二叉树的存储结构1. 顺序存储重要的基本操作非完全二叉树2. 链式存储逆向…...
构建稳健的微服务架构:关键的微服务设计原则和最佳实践
在现代软件开发中,微服务架构正逐渐成为构建复杂应用程序的首选方法之一。微服务架构的核心理念是将应用程序划分为一系列小型、自治的服务,每个服务专注于一个特定的业务功能。然而,要实现一个稳健的微服务架构并不仅仅是将功能拆分成微服务…...

消息队列常见问题(1)-如何保障不丢消息
目录 1. 为什么消息队列会丢消息? 2. 怎么保障消息可靠传递? 2.1 生产者不丢消息 2.2 服务端不丢消息 2.3 消费者不丢消息 3. 消息丢失如何快速止损? 3.1 完善监控 3.2 完善止损工具 1. 为什么消息队列会丢消息? 现在主流…...
Circle of Mistery 2023牛客暑期多校训练营5 B
登录—专业IT笔试面试备考平台_牛客网 题目大意:给出一个n个数的数组a,求一个排列,使其形成的其中一个置换环上的数的和>k,并使产生的逆序对数量最少 1<n<1e3;-1e6<k<1e6;-1e6<ai<1e6 tips:关于置换环是什…...
VC9、VC10、VC11等等各对应什么版本的Visual Studio,以及含义
文章目录 1、_MSC_VER 定义编译器的版本2、示例 1、_MSC_VER 定义编译器的版本 MS VC 15.0 _MSC_VER 1910 (Visual Studio 2017) MS VC 14.0 _MSC_VER 1900 (Visual Studio 2015) MS VC 12.0 _MSC_VER 1800 (VisualStudio 2013) MS VC 11.0 _MSC_VER 1700 (VisualStudio…...
两数相加 LeetCode热题100
题目 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这两个数都不会…...
Python基础 P2数字类型与优先级进阶练习
文章目录 Python基础 P2数字类型与优先级进阶练习1.闰年判断器2.进制转换及求和3.单位转换 Python基础 P2数字类型与优先级进阶练习 1.闰年判断器 简介 对于闰年的判断就是判断输入的内容类型是否符合要求,然后通过逻辑判断和运算得出该年份是否为闰年 举个栗子 …...
CAPL通过继电器实现CAN容错性自动化测试
系列文章目录 文章目录 系列文章目录前言一、环境搭建1.硬件环境2.软件环境3.继电器线路连接图:二、容错性测试方法1.CAN_H与CAN_L短路2.CAN_H与GND短路3.CAN_L与GND短路4.CAN_H与电源短路5.CAN_L与电源短路6.CAN_H断路7.CAN_L断路三、CAPL自动化测试1.测试用例目录2.测试报告…...

elasticsearch 配置用户名和密码
无密码的其他配置项在:https://blog.csdn.net/Xeon_CC/article/details/132064295 elasticsearch.yml配置文件: xpack.security.enabled: true xpack.security.http.ssl.enabled: true xpack.security.http.ssl.keystore.path: /path/to/elastic-certi…...

侯捷 C++面向对象编程笔记——9 复合 委托
9 复合 委托 9.1 Composition 复合 类似于c中结构里有结构——class里有class deque 是一个已经存在的功能很多的类(两头进出的队列);利用deque的功能来实现queue的多种操作 该例只是复合的一种情况——设计模式 Adapter 9.1.1 复合下的构造…...

状态模式——对象状态及其转换
1、简介 1.1、概述 在软件系统中,有些对象也像水一样具有多种状态,这些状态在某些情况下能够相互转换,而且对象在不同的状态下也将具有不同的行为。为了更好地对这些具有多种状态的对象进行设计,可以使用一种被称为状态模式的设…...
Linux一阶段复习
Linux之父是林纳斯本纳第克特托瓦兹 Apache发布目录:/var/www/html nginx发布目录:/usr/share/nginx/html/ 配置dns的文件 : /etc/resolv.conf nginx的配置文件:/etc/nginx/ yum源的配置文件:/etc/yum.repos.d/ …...
宝塔Linux面板怎么升级?升级命令及失败解决方法
宝塔Linux面板怎么升级到新版本?root账号ssh登录到云服务器后,执行宝塔Linux面板升级命令即可搞定,新手站长分享宝塔Linux面板升级命令: 宝塔面板升级到新版本 1、使用root账号ssh登录到云服务器上 ssh root你的云服务器ip地址…...
前端面试的性能优化部分(6)每天10个小知识点
目录 系列文章目录前端面试的性能优化部分(1)每天10个小知识点前端面试的性能优化部分(2)每天10个小知识点前端面试的性能优化部分(3)每天10个小知识点前端面试的性能优化部分(4)每天…...

2023年 Java 面试八股文(20w字)
目录 第一章-Java基础篇 1、你是怎样理解OOP面向对象 难度系数:⭐ 2、重载与重写区别 难度系数:⭐ 3、接口与抽象类的区别 难度系数:⭐ 4、深拷贝与浅拷贝的理解 难度系数:⭐ 5、sleep和wait区别 难度系数&a…...
银河麒麟服务器ky10-server在线一键安装docker
脚本代码 # ---------------在线安装docker------------------- yum install docker -y # 修改docker拉取源为国内 rm -rf /etc/docker mkdir -p /etc/docker touch /etc/docker/daemon.json cat >/etc/docker/daemon.json<<EOF{"registry-mirrors": [&q…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...

以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...

如何应对敏捷转型中的团队阻力
应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中,明确沟通敏捷转型目的尤为关键,团队成员只有清晰理解转型背后的原因和利益,才能降低对变化的…...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...