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

C++:谈谈单例模式的多种实现形式

文章目录

      • 实现 1:静态成员
      • 实现 2:atexit + 懒汉模式
      • 实现 3:原子变量 + 懒汉模式
      • 实现4:atexit + 饿汉模式
      • * 实现5:magic static

单例模式:保证一个类仅有一个实例,并提供一个该实例的全局访问点。

  • 稳定点:类只有一个实例,提供全局
  • 变化点:有多个类都是单例,能否复用代码

实现 1:静态成员

  • 构造函数和析构函数私有化
  • 禁掉拷贝构造、拷贝赋值、移动构造、移动赋值
  • 静态成员函数
  • 静态私有成员

前两点针对唯一实例,后两点针对全局访问。

问题:当程序结束后,不会调用析构函数,堆上资源无法释放,内存泄漏。虽然程序结束后,堆上所有的数据被销毁,但是无法保存需要持久化的数据。

class Singleton {
public:// 静态成员函数:全局访问点static Singleton* getInstance() {if(nullptr == _pInstance) {_pInstance = new Singleton();}return _pInstance;}private:// 构造函数和析构函数私有化Singleton(); ~Singleton(); // 禁掉拷贝构造、拷贝赋值、移动构造、移动赋值Singleton(const Singleton &) = delete;Singleton& operator=(const Singleton&) = delete;Singleton(Singleton &&) = delete;Singleton& operator=(Singleton &&) = delete;private:// 静态成员:静态成员函数只能访问静态成员static Singleton* _pInstance;};// 静态成员需要初始化
Singleton* Singleton::_instance = nullptr; 

实现 2:atexit + 懒汉模式

atexit 函数:在进程结束后利用回调函数自动释放堆空间。

/* 
功能:注册给定函数,并在进程结束后调用该函数
参数:函数指针,指向被调用的函数(返回值、参数均为void) 
*/
#include <stdlib.h>
int atexit(void (*function)(void));

利用这一特性,在进程结束时 atexit 函数调用销毁函数,完成析构工作。

问题:atexit 函数本身安全,但是多线程环境下。存在线程安全问题。

static Singleton* getInstance() {if(nullptr == _pInstance) {// 问题:多个并发线程可能同时创建对象_pInstance = new Singleton();atexit(Singleton::Destructor);}return _pInstance;
}

为保证线程安全,需要加锁。对于加锁操作,只有第一次写操作创建对象的时候,需要加锁;其他时候都是读操作,没有必要加锁。因此这里在实现的时候可以采用双重检测 double check的技巧。

#include <stdlib.h>
class Singleton {
public:static Singleton* getInstance() {if(nullptr == _pInstance) {_pInstance = new Singleton();// 线程安全,双重检测:double check      if (nullptr == _pInstance) {std::lock_guard<std::mutex> lock(_mutex);if (nullptr == _pInstance) {// 问题:多线程环境下,cpu reorder_pInstance = new Singleton();atexit(Singleton::Destructor);}}return _pInstance;// 注册回调函数,进程结束后,调用销毁函数atexit(Singleton::Destructor);}return _pInstance;}private:Singleton(); ~Singleton(); Singleton(const Singleton &) = delete;Singleton& operator=(const Singleton&) = delete;Singleton(Singleton &&) = delete;Singleton& operator=(Singleton &&) = delete;// 注册销毁函数为atexit的回调函数,用于在进程结束后释放堆空间static void Destructor() {if (nullptr != _instance) { delete _instance;_instance = nullptr;}}private:static Singleton* _pInstance;};Singleton* Singleton::_instance = nullptr; 

问题:new 操作符指令重排

C++ 98 表达单线程语义。而在多核多线程的情况下,若 cpu 指令重排,例如:对于 new 运算符的指令执行:分配内存、调用构造函数、返回指针。若发生 cpu 指令重排,会优化为分配内存、返回指针,却还没有调用构造函数初始化数据。此时,若有其他线程访问,可能造成程序的崩溃。

实现 3:原子变量 + 懒汉模式

C++ 11:多线程语义,cpu 指令重排,提供同步原语:原子变量、内存屏障等

原子变量解决

  • 原子性问题
  • 可见性问题:load 可以看见其他线程最新操作的数据, store 修改数据让其他线程可见
  • 执行序问题:memory_order_acuire不能重排指令,memory_order_release松散指令,可以重排指令。

内存屏障(内存栅栏)解决

  • 可见性问题
  • 执行序问题

使用原子变量解决原子性、可见性、执行序

class Singleton {
public:static Singleton * GetInstance() {Singleton* tmp = _instance.load(std::memory_order_acquire);if (tmp == nullptr) {std::lock_guard<std::mutex> lock(_mutex);tmp = _instance.load(std::memory_order_acquire);if (tmp == nullptr) {tmp = new Singleton;_instance.store(tmp, memory_order_release);atexit(Destructor);}}return tmp;}
...static std::atomic<Singleton*> _instance;static std::mutex _mutex;
};
std::atomic<Singleton*> Singleton::_instance; // 静态成员需要初始化
std::mutex Singleton::_mutex; 			     // 互斥锁初始化 

改进:若构造函数中存在其他原子性操作,则可以使用松散的指令执行方式,提升运行速度。使用内存屏障,避免 tmp 指针在 new 操作未执行完就返回给用户。

  • 原子变量解决:原子性、可见性
  • 内存栅栏解决:执行序
class Singleton {
public:static Singleton * GetInstance() {Singleton* tmp = _instance.load(std::memory_order_relaxed);// 获取内存屏障std::atomic_thread_fence(std::memory_order_acquire);if (tmp == nullptr) {std::lock_guard<std::mutex> lock(_mutex);tmp = _instance.load(std::memory_order_relaxed);if (tmp == nullptr) {tmp = new Singleton;// 释放内存屏障std::atomic_thread_fence(std::memory_order_release);_instance.store(tmp, std::memory_order_relaxed);atexit(Destructor);}}return tmp;}...static std::atomic<Singleton*> _instance;static std::mutex _mutex;
};
std::atomic<Singleton*> Singleton::_instance; // 静态成员需要初始化
std::mutex Singleton::_mutex; 			     // 互斥锁初始化 

问题:代码复杂,书写困难。

实现4:atexit + 饿汉模式

懒汉模式是延迟加载,饿汉模式是提前加载。当系统开始运行,加载类的时候就初始化类实例,其他线程无法再创建实例,实现线程安全。

class Singleton {
public:static Singleton* Singleton::getInstance() {if(nullptr == _pInstance) {_pInstance = new Singleton();atexit(Singleton::Destructor);}return _pInstance;}   
...
};// 全局初始化,使其在进程创建之前就不为空,防止子进程创建对象
Singleton* Singleton::_instance = getInstance();

问题:无论是否需要该类实例,都必须提前创建。

* 实现5:magic static

源自:C++ effective,C++ 11 magic static 特性,参考官方文档:静态局部变量,推荐使用。

  • 如果变量在初始化的时候,并发同时进入声明语句,并发线程会阻塞等待初始化结束。线程安全。
  • 静态局部变量首次经过它的声明才会被初始化,在其后所有的调用中,声明都会被跳过。

因此,使用定义在栈上的局部静态变量保存单例对象,具备所有优点:

  • 延迟加载
  • 系统自动调用析构函数,回收内存
  • 没有 new 操作带来的 cpu reorder 操作
  • 线程安全
class Singleton {
public:static Singleton& GetInstance() {// magic static// 定义在栈上的局部静态变量,进程结束后自动释放static Singleton instance;return instance;}private:Singleton(); ~Singleton(); Singleton(const Singleton &) = delete;Singleton& operator=(const Singleton&) = delete;Singleton(Singleton &&) = delete;Singleton& operator=(Singleton &&) = delete;
};

相关文章:

C++:谈谈单例模式的多种实现形式

文章目录实现 1&#xff1a;静态成员实现 2&#xff1a;atexit 懒汉模式实现 3&#xff1a;原子变量 懒汉模式实现4&#xff1a;atexit 饿汉模式* 实现5&#xff1a;magic static单例模式&#xff1a;保证一个类仅有一个实例&#xff0c;并提供一个该实例的全局访问点。 稳…...

【Spring Cloud Alibaba】007-Nacos 配置*

【Spring Cloud Alibaba】007-Nacos 配置* 文章目录【Spring Cloud Alibaba】007-Nacos 配置*一、概述1、概述2、对比 spring cloud config二、基本使用1、在管理界面新建配置2、启动权限3、 搭建 nacos-config 服务第一步&#xff1a;引入依赖第二步&#xff1a;修改 yaml 配置…...

《安富莱嵌入式周报》第304期:开源硬件耳机设计,AI单片机STM32N6已确定为M55内核,另外还有新品STM32H5, H50X, H7R, H7S发布

往期周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 更新一期视频教程&#xff1a; 第6期ThreadX视频教程&#xff1a;图文并茂吃透RTOS运行机制&#xff0c;任务管理&…...

vuex篇

1.简介(1)vuexVuex 是一个专为 Vue.js 应用程序开发的状态管理模式 库vuex是为vue.js开发的状态管理模式、组件状态集中管理(2)单页面数据流状态发生变化, 视图就重新渲染state发生变化时, 导致view视图发生改变, 视图通过操作action行为, 又会使得state状态发生变化(3)使用场…...

嵌入式开发:在嵌入式应用程序中混合C和C++

许多嵌入式应用程序仍使用c语言编写&#xff0c;但越来越多的嵌入式开发人员现在使用C语言编写程序。某些应用程序甚至共享这两种语言。这有意义吗?C是嵌入式应用中最常用的编程语言。多年来&#xff0c;人们一直期待着向C过渡&#xff0c;但过渡速度相当缓慢。但是&#xff0…...

【2023/图对比/增强】MA-GCL: Model Augmentation Tricks for Graph Contrastive Learning

如果觉得我的分享有一定帮助&#xff0c;欢迎关注我的微信公众号 “码农的科研笔记”&#xff0c;了解更多我的算法和代码学习总结记录。或者点击链接扫码关注【2023/图对比/增强】MA-GCL: Model Augmentation Tricks for Graph Contrastive Learning 【2023/图对比/增强】MA-…...

TensorBoard自定义修改单条及多条曲线颜色

在深度学习可视化训练过程中&#xff0c;曲线颜色是随机的&#xff0c;想要将好看的曲线颜色图放到论文中&#xff0c;就得自定义曲线颜色&#xff0c;具体方法见下文。 目录一、下载svg文件二、修改svg文件三、修改后曲线颜色对比四、总结一、下载svg文件 在TensorBoard界面中…...

时间和空间复杂度

文章目录 前言 一、算法效率 1.如何评判算法效率&#xff1f; 2.算法的复杂度 二、时间复杂度 1.时间复杂度的定义 2. 大O的渐进表示法 三、空间复杂度 总结 前言 本文章讲解时间与空间复杂度 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、算法…...

关于Linux下调试

关于Linux下调试 无论是内核&#xff08;操作系统&#xff09;还是应用程序&#xff0c;都存在需要调试的情况。 所谓工欲善其事&#xff0c;必先利其器。一个好的称手的工具&#xff0c;对于快速分析问题、定位问题&#xff0c;提高效率&#xff0c;非常有帮助。 除了工具&a…...

理解TP、FP、TN、FN

概念定义 按照常用的术语&#xff0c;将两个类分别称为正类 (positive) 和 负类 (negative)。使用数学表示&#xff1a; 1表示正类 &#xff0c; -1 表示负类。 正类通常是少数类&#xff0c;即样本较少的类&#xff08;例如有缺陷的零件&#xff09; 负类通常是多数类&#x…...

软考中级有用吗

当然有用了&#xff01; 软考“简历”&#xff1a;计算机软件资格考试在全国范围内已经实施了二十多年&#xff0c;近十年来,考试规模持续增长&#xff0c;截止目前,累计报考人数约有五百万人。该考试由于其权威性和严肃性&#xff0c;得到了社会各界及用人单位的广泛认同&…...

计算机网络之IP协议(详解

网络层主管地址管理与路由选择。而IP协议就是网络层中一个非常重要的协议。它的作用就是在复杂的网络环境中确定一个合适的路径。IP协议头格式4位版本号(version) 指定IP协议的版本&#xff0c;目前只有两个版本&#xff1a;IP v4和IP v6.对于IP v4来说&#xff0c;这个值就是4…...

Kubernetes之探针probe

deployment只保证pod的状态为running。如果pod状态是running&#xff0c;但是里面丢失了文件&#xff0c;导致用户不能访问数据&#xff0c;则deployment是不管用的&#xff0c;此时就需要probe来检测pod是否正常工作。 probe是定义在容器里的&#xff0c;可以理解为容器里加的…...

高性能低功耗4口高速USB2.0 HUB NS1.1S 兼容FE1.1

NS1.1S是一款高性能、低功耗4口高速 USB2.0 HUB 控制器&#xff0c;上行端口兼容高速 480MHz和全速12MHz两种模式&#xff0c;4个下行端口兼容高速480MHz、全速12MHz、低速1.5MHz三种模式。 NS1.1S采用状态机单事务处理架构&#xff0c;而非单片机架构&#xff0c;多个事务缓冲…...

通过VS Code轻松连接树莓派

如果您正在使用树莓派作为开发平台&#xff0c;那么通过远程连接VS Code到树莓派是非常方便的一种方法。这样&#xff0c;您可以在Windows或macOS等计算机上开发和测试代码&#xff0c;而不必在树莓派上进行。 以下是通过VS Code远程连接到树莓派的步骤&#xff1a; 1.安装Re…...

图纸等敏感文件数据外发时 如何确保效率和安全性?

很多企业随着业务的发展&#xff0c;需要频繁的与外部供应商、合作伙伴之间进行数据的交换和使用。尤其是制造型企业&#xff0c;可能每天都要与几十、上百家供应商及合作伙伴进行产品数据交换。目前&#xff0c;大多数企业已经在内部实施了PDM/PLM系统&#xff0c;实现了对组织…...

2023年CDGA考试-第4章-数据架构(含答案)

2023年CDGA考试-第4章-数据架构(含答案) 单选题 1.请从下列选项中选择不属于数据架构师职责的选项 A.确保数据架构和企业战略及业务架构一致 B.提供数据和组件的标准业务词汇 C.设计企业数据模型 D.整合企业数据架构蓝图 答案 C 2.请从下列选项中选择不属于企业数据架构…...

理解随机游走

随机游走 基本思想 从一个或一系列顶点开始遍历一张图。在任意一个顶点&#xff0c;遍历者将以概率1-a游走到这个顶点的邻居顶点&#xff0c;以概率a随机跳跃到图中的任何一个顶点&#xff0c;称a为跳转发生概率&#xff0c;每次游走后得出一个概率分布&#xff0c;该概率分布…...

mqtt协议1- 简介和报文格式

文章目录1.mqtt协议1: 简介和报文格式1.1.MQTT概念1.2.数据2.控制报文格式2.1.MQTT数据包结构2.2.固定头2.2.1.控制报文类型2.2.2.标志FLag2.2.3.剩余长度2.3.可变头2.4.有效载荷Payload消息体安全QoS(Quality of Service levels)ref:1.mqtt协议1: 简介和报文格式 Message Que…...

前端用动画快速实现骨架屏效果

一、动画的语法 1.定义动画 keyframes 自定义动画名称 {// 开始from {transform: scale(1);}// 结束to {transform: scale(1.5);} }// 或者还可以使用百分比定义keyframes 动画名称 {// 开始0% {transform: scale(1);}// 结束100% {transform: scale(1.5);} } 2.调用 anima…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

Kafka主题运维全指南:从基础配置到故障处理

#作者&#xff1a;张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1&#xff1a;主题删除失败。常见错误2&#xff1a;__consumer_offsets占用太多的磁盘。 主题日常管理 …...

Axure 下拉框联动

实现选省、选完省之后选对应省份下的市区...

6.9-QT模拟计算器

源码: 头文件: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);…...

内窥镜检查中基于提示的息肉分割|文献速递-深度学习医疗AI最新文献

Title 题目 Prompt-based polyp segmentation during endoscopy 内窥镜检查中基于提示的息肉分割 01 文献速递介绍 以下是对这段英文内容的中文翻译&#xff1a; ### 胃肠道癌症的发病率呈上升趋势&#xff0c;且有年轻化倾向&#xff08;Bray等人&#xff0c;2018&#x…...