当前位置: 首页 > 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…...

Python入门(未完待续。。。)

认识python 解释型&#xff08;写完直接运行&#xff09;、面向对象的高级编程语言&#xff1b;开源免费、支持交互式、可跨平台移植的脚本语言&#xff1b;优点&#xff1a;开源、易于维护、可移植、简单优雅、功能强大、可扩展、可移植&#xff1b;缺点&#xff1a;解释型→运…...

注解配置SpringMVC

使用配置类和注解代替web.xml和Spring和SpringMVC配置文件的功能。创建初始化类&#xff0c;代替web.xmlSpring3.2引入了一个便利的WebApplicationInitializer基础实现&#xff0c;名为AbstractAnnotationConfigDispatcherServletInitializer&#xff0c;当我们的类扩展了Abstr…...

多项新规重磅发布,微信视频号近期需要关注这几点

随着功能的完善和内容生态的丰富&#xff0c;视频号逐渐放慢产品更新频率&#xff0c;将重点放到商家准入标准、创作者扶持计划上来&#xff0c;本期我们将更侧重解读平台新规&#xff0c;帮助大家了解行业动向&#xff0c;把握最新趋势。01 视频号小店结算规则修订 取消48小时…...

Java调用第三方http接口的方式

1. 概述 在实际开发过程中&#xff0c;我们经常需要调用对方提供的接口或测试自己写的接口是否合适。很多项目都会封装规定好本身项目的接口规范&#xff0c;所以大多数需要去调用对方提供的接口或第三方接口&#xff08;短信、天气等&#xff09;。 在Java项目中调用第三方接…...

【跟我一起读《视觉惯性SLAM理论与源码解析》】第五章第六章 对极几何图优化库的使用

极平面&#xff0c;极点&#xff0c;极线的概念对极几何&#xff0c;对极约束的概念&#xff0c;通过叉积以及点积的性质推导单应矩阵以及基础矩阵光束平差法BA是LSAM中常用的非线性优化方法一个图由若干个顶点以及这些顶点连接的边构成&#xff0c;顶点通常是待优化的变量例如…...

从没想过开源 API 工具的 Mock 功能,这么好用

很多时候&#xff0c;接口尚未开发完成&#xff0c;在系统交互双方定义好接口之后&#xff0c;我们可以提前进行开发和测试&#xff0c;并不依赖上游系统的开发实现。 通过使用Mock模拟数据接口&#xff0c;我们即可在只开发了UI的情况下&#xff0c;无须服务端的开发就可以进行…...

智慧教室--智能管控系统

智慧教室系统是一款基于AIOT数字化平台的智能教育解决方案&#xff0c;该系统实现了全面数字化、自动化管理和智能化控制&#xff0c;可大大提高教学效率和质量&#xff0c;为学生带来更加优质的教育体验。智能管控是智慧教室系统的核心功能之一。通过物联网技术&#xff0c;将…...

React的useLayoutEffect和useEffect执行时机有什么不同

我们先看下 React 官方文档对这两个 hook 的介绍&#xff0c;建立个整体认识 useEffect(create, deps): 该 Hook 接收一个包含命令式、且可能有副作用代码的函数。在函数组件主体内&#xff08;这里指在 React 渲染阶段&#xff09;改变 DOM、添加订阅、设置定时器、记录日志以…...

C语言中#include<...>和#include“...“的区别

C语言文章更新目录 C语言学习资源汇总&#xff0c;史上最全面总结&#xff0c;没有之一 C/C学习资源&#xff08;百度云盘链接&#xff09; 计算机二级资料&#xff08;过级专用&#xff09; C语言学习路线&#xff08;从入门到实战&#xff09; 编写C语言程序的7个步骤和编程…...

ubuntu本地访问nas

需求 本地磁盘空间太小&#xff0c;本地网络里有个nas&#xff0c;希望将nas作为数据盘挂载到本地使用。 方法1 基于sftp访问nas 首先nas设置时要打开sftp访问功能。 然后用ubuntu桌面访问服务器的功能登录sftp&#xff0c;类似如下指令 sftp://user192.168.0.100 ubuntu下…...