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

C++ 实现单例模式

单例模式

单例模式确保一个类只有一个实例,并提供一个全局访问点

创建单一实例

怎么让某个类只能创建一个实例?

思路:将类的构造函数私有,然后提供一个静态方法访问对象。调用类内成员函数需要对象,但我们又无法创建出对象,所以要将该接口函数声明为静态函数,这样就可以在类外使用类名调用。

class Singleton {public:static Singleton* GetInstance() {if (_uniqueInstance == nullptr) {_uniqueInstance = new Singleton;}return _uniqueInstance;}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;private:Singleton() = default;static Singleton* _uniqueInstance;
};Singleton* Singleton::_uniqueInstance = nullptr;

我们用一个指针保存创建的单例对象,并在第一次调用 GetInstance 时创建对象,以后就直接返回该单例对象。

多线程下的问题

那么问题来了,如果一个线程判断指针为空,线程创建单例对象。另一个线程在上一个线程创建返回之前,同样进行了判断也得到了指针为空的结果,同样进入创建对象。此时,一个单例对象竟被创建了两次。我们可以采用加锁的方式,让多线程互斥地访问该部分。

class Singleton {public:static Singleton* GetInstance()  {_mtx.lock();if (_uniqueInstance == nullptr) {_uniqueInstance = new Singleton;}_mtx.unlock();return _uniqueInstance;}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;private:Singleton() = default;static Singleton* _uniqueInstance;static mutex _mtx;
};Singleton* Singleton::_uniqueInstance = nullptr;
mutex Singleton::_mtx;

此时又有新的麻烦了,明明我们只需要在第一次进入时互斥,后续访问就不再需要了。采用加锁的方式将大大降低程序的运行效率,这在性能要求高的程序中是不可容忍的。

双加锁

一个比较常见的解决方式是双加锁,首先检查实例是否已经创建了,如果还没创建,才进行互斥控制。这样一来,就只有第一次会进行互斥控制。

static Singleton* GetInstance()  {// 使用 double-check 方式加锁,保证效率和线程安全if (_uniqueInstance == nullptr) {_mtx.lock();if (_uniqueInstance == nullptr) {_uniqueInstance = new Singleton;}_mtx.unlock();}return _uniqueInstance;
}

但实际上上面的代码还是有问题的。问题的来源是 CPU 的乱序执行,C++ 里的 new 包含了两个步骤:

  1. 调用 ::operator new 分配内存
  2. 调用构造函数

所以 ptr = new T 包含了三个步骤:

  1. 调用 ::operator new 分配内存
  2. 在内存的位置上调用构造函数
  3. 将内存的地址赋值给 ptr

在这三步中,2 和 3 的顺序是可以交换的。也就是说,有可能:有一个线程分配了内存并将地址赋值给 ptr 了,但还没有初始化该内存。另一线程检测 ptr 不为空,就直接拿去使用了,这时可能引起不可预料的结果。

通常情况下,可以调用 CPU 提供的一条指令来解决该情况,这指令被称为 barrier。一条 barrier 指令会阻止 CPU 将该指令交换到 barrier 之后,也不能将之后的指令交换到 barrier 之前。

#define barrier() __asm__ volaticle("lwsync") 
T* ptr = nullptr;T* GetInstance() {if (nullptr == ptr) {lock();if (nullptr == ptr) {T* tmp = new T;barrier();ptr = tmp;}unlock();}return ptr;
}

急切创建实例

如果程序总是创建并使用单例实例,或者在创建和运行时方面的负担不太繁重,你可以急切创建此单例。

class Singleton {public:static Singleton* GetInstance() {return &_instance;}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;private:Singleton() = default;static Singleton _instance;
};Singleton Singleton::_instance;

如果是 Java,上述代码形式没什么问题。JVM 在加载这个类时马上创建此唯一的单例实例。JVM 保证在任何线程访问 uniqueInstance 静态变量之前,一定先创建此实例。

non-local static 对象初始化次序问题

但在 C++ 中还有「不同编译单元内定义的 non-local static 对象」的初始化次序问题。函数内的 static 对象称为 local static 对象,其他 static 对象称为 non-local static 对象。

编译单元(translation unit)是指产出单一目标文件(single object file)的那些源码。基本上它是单一源码文件加上其所含入的头文件。

如果某编译单元内的某个 non-local static 对象的初始化动作使用了另一编译单元内的某个 non-local static 对象,它所用到的这个对象可能尚未被初始化,因为 C++ 对「定义于不同编译单元内的 non-local static 对象」的初始化次序并无明确定义。

幸运的是一个小小的设计便可以解决这个问题。唯一需要做的是:将每个 non-local static 对象搬到自己的专属函数内(该对象在此函数内被声明为 static)。这些函数返回一个 reference 指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。

这个手法的基础在于:C++ 保证,函数呢的 local static 对象会在「该函数被调用期间」「首次遇上该对象的定义式」时被初始化。

class Singleton {public:static Singleton& getInstance() {static Singleton inst;return inst;}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;private:Singleton() = default;
};

相关文章:

C++ 实现单例模式

单例模式 单例模式确保一个类只有一个实例,并提供一个全局访问点。 创建单一实例 怎么让某个类只能创建一个实例? 思路:将类的构造函数私有,然后提供一个静态方法访问对象。调用类内成员函数需要对象,但我们又无法…...

Java多线程--线程安全问题练习题

文章目录 (1)练习题1(2)练习题2(3)练习题3 现在咱们线程一共说了这么几件事情,如下: 具体文章见专栏。 接下来看几个练习题吧。 (1)练习题1 🌋题…...

PHY6252低成本Mesh组网蓝牙芯片

超低成本MESH组网蓝牙芯片PHY6252蓝牙Mesh组网简介 蓝⽛Mesh⽹络使⽤,依赖于低功耗蓝⽛(BLE)。低功耗蓝⽛技术是蓝⽛Mesh使用的无线通信协议栈,蓝牙BR/EDR能够与实现一台设备到另一台设备的连接和通信,建立“一对一”的关系,大多数…...

红外图像中两点校正的增益系数与偏置系数的计算公式推导

增益系数(K) 根据两个温度下的响应值,可求得各响应单元的响应曲线(即斜率),累加所有曲线的斜率求平均斜率值。 平均斜率值与各响应单元的斜率的比值即为该单元的K增益系数。结论:某单元的增益系…...

C++/MFC:在窗体Form(Dialog)中多个编辑框时,在输入时将回车解释为TAB键,将输入焦点移到下一个编辑框的方法

很多时候,为了输入方便,常用的做法,就是将回车键解释为将输入焦点移动到下一个编辑框中。就像是我的VxTerm中的快速连接输入一样: VxTerm是一个国产化替代的SSH工具,可以从本站的资源中免费下载并且免费使用&#xff…...

鸿蒙南向开发——GN快速入门指南

运行GN(Generate Ninja) 运行gn,你只需从命令行运行gn,对于大型项目,GN是与源码一起的。 对于Chromium和基于Chromium的项目,有一个在depot_tools中的脚本,它需要加入到你的PATH环境变量中。该脚本将在包含当前目录的…...

PyCharm常用快捷键和设置

Ctrl Space 基本的代码完成(类、方法、属性) Ctrl Alt Space 快速导入任意类 Ctrl Shift Enter 语句完成 Ctrl P 参数信息(在方法中调用参数) Ctrl Q 快速查看文档 F1 外部文档 Shift F1 外部文档…...

Unity - 调节camera物理相机参数(HDRP)

在 “Hierarchy” 右键 -> Volume -> Global Volume new 一个 profile, 设置Mode为Pysical Camera 再点击camera组件,这时候设置 ISO、Shutter Speed、Aperture等参数值还会有效。...

@JsonIgnore的使用及相关问题的解决

目录 1 前言 2 对比及其使用方法 3 遇到的相关问题及解决方法 1 前言 在我们编写的后端项目中,有时候可能需要将某个实体类以JSON格式传送给前端,但是其中可能有部分内容我们并不想传送,这时候我们选择将这部分内容变成Null,这…...

万户 ezOFFICE SendFileCheckTemplateEdit.jsp SQL注入漏洞

0x01 产品简介 万户OA ezoffice是万户网络协同办公产品多年来一直将主要精力致力于中高端市场的一款OA协同办公软件产品,统一的基础管理平台,实现用户数据统一管理、权限统一分配、身份统一认证。统一规划门户网站群和协同办公平台,将外网信息维护、客户服务、互动交流和日…...

自建DNS劫持服务器,纯内网劫持PS5,屏蔽更新,自动hen

背景:目前PS5首次折腾必须要连外网,还要改DNS,除非使用ESP8266/32, 本文的方法是完全不改DNS,不使用ESP8266,不连接外网的情况下自动折腾 能实现什么: 1.折腾全程不连接外网 2.完全自建hen服务器&#xff…...

C语言王道第八周一题

Description 初始化顺序表(顺序表中元素为整型),里边的元素是 1,2,3,然后通过 scanf 读取一个元素(假如插入的是 6),插入到第 2 个位置,打印输出顺序表,每个 元素占 3 个…...

探索1688店铺所有商品API接口:一键获取海量数据,开启商业智能新篇章

1688店铺所有商品API接口技术详解 一、概述 1688店铺所有商品API接口是阿里巴巴提供的一套应用程序接口,允许第三方开发者获取指定1688店铺下的所有商品信息。通过使用这个接口,开发者可以获取到店铺内所有商品的列表、详情、属性等信息,从…...

使用Win32API实现贪吃蛇小游戏

目录 C语言贪吃蛇项目 基本功能 需要的基础内容 Win32API 介绍 控制台程序部分指令 设置控制台窗口的长宽 设置控制台的名字 控制台在屏幕上的坐标位置结构体COORD 检索指定标准设备的句柄(标准输入、标准输出或标准错误) 光标信息结构体类型CONSOLE_CUR…...

力扣0114——二叉树展开为链表

[二叉树展开为链表] 难度:中等 题目描述 给你二叉树的根结点 root ,请你将它展开为一个单链表: 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。展开后的单…...

FPGA硬件架构

1.Xilinx FPGA是异构计算平台(所谓异构,就是有很多不同的部分组成):CLB,BRAM,DSP 2. 软核: 把经过功能验证的、可综合的、实现后电路结构总门数在五千门以上的Verilog HDL模型称为软核(soft core)。 硬核: 把在某一…...

spring boot 嵌入chatGPT步骤

一、需要良好的网络 二、需要在OpenAI官网https://openai.com/注册用户,并获取一个api-key,sk开头的 验证是否可用网站:http://tools.lbbit.top/check_key_valid/ 三、spring boot 配置文件 openai.proxyHost127.0.0.1 openai.proxyPort7890…...

博云科技与中科可控全面合作,探索前沿金融科技新机遇

2024年1月26日,博云科技与中科可控在昆山高新区成功举办合作签约仪式。昆山市委常委、昆山高新区党工委书记孙道寻、中科可控董事长聂华、博云科技董事长花磊等领导出席了本次签约仪式。 中科可控将利用其在先进计算和智造领域的优势,为博云科技提供有关…...

十一、常用API——练习

常用API——练习 练习1 键盘录入:练习2 算法水题:练习3 算法水题:练习4 算法水题:练习5 算法水题: 练习1 键盘录入: 键盘录入一些1~100之间的整数,并添加到集合中。 直到集合中所有数据和超过2…...

基于ssm和微信小程序的健身房私教预约管理系统

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式 🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 &…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...

CTF show Web 红包题第六弹

提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 ​ 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)&#xff0…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论

路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...

群晖NAS如何在虚拟机创建飞牛NAS

套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...

android13 app的触摸问题定位分析流程

一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...