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;
}

急切创建实例

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

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 静态变量之前,一定先创建此实例。

但在 C++ 中还有潜在的问题,简单点说就是当你调用 GetInstance() 获得了对象的引用,你打算用这个单例对象初始化另一个单例对象,就有可能导致错误,下文将解释原因。

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;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/665222.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

css新手教程

css新手教程 课程:14、盒子模型及边框使用_哔哩哔哩_bilibili 一.什么是CSS 1.什么是CSS Cascading Style Sheet 层叠样式表。 CSS:表现(美化网页) 字体,颜色,边距,高度,宽度&am…

Linux信号详解~

目录 前言 一、初识信号 二、信号的概念 三、信号的发送与捕捉 3.1 信号的发送 3.1.1 kill 命令 3.1.2 kill 函数 3.1.3 raise函数 3.1.4 abort函数 3.2 信号的捕捉 3.2.1 signal函数 3.2.2 sigaction函数 3.2.3 图示 四、信号的产生 4.1 硬件异常产生信号 4.2 …

CMake Msys2 搭配vscode

(一)MSYS2介绍 MSYS2(Minimal SYStem 2)是一个集成了大量的GNU工具链、工具和库的开源软件包集合。它提供了一个类似于Linux的shell环境,可以在Windows系统中编译和运行许多Linux应用程序和工具。 MSYS2基于MinGW-w64平台,提供了…

Linux---进程间通信 | 管道 | PIPE | MKFIFO | 共享内存 | 消息队列

管道 管道是UNIX中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的数据流称为一个管道。 一个文件,可以被多个进程打开吗?可以,那如果一个进程打开文件,往文件里面写数据,另一个进程打开文…

MySQL 中 int(1) 和 int(10) 会影响存储的长度吗

一、MySQL 中 int(1) 和 int(10) 在MySQL数据库设计中,经常会遇到 int 类型的字段,并会习惯性的指定长度,比如: int(1) 和int(10),而一些新手可能会误解它们之间的关系,认为 int(10) 能够存储更多的数据。…

Android Camera2 API 后台服务

最近在搞CameraAPP需要将Camera2弄成一个后台服务,发现跟预览的Activity没多大变动只是加了Service,和一些简单的修改。之前的公司也用到Camera2,发现用到的时候还是蛮多的所以记录一下,代码在文章末尾 camera2的结构如下&#x…

Peter算法小课堂—Dijkstra最短路算法

大家好,我们人见人爱、花见花开、车见车爆胎的Peter Pan来啦,hia~hia~hia。今天,我们今天来学习毒瘤的最短路算法啦。啊这……什么是Dijkstra算法?长文警告⚠ 正经点啊 手算样例 大家思考一下,你在手算样例的时候&am…

企业申请sectigo ip https证书

Sectigo(原名Comodo,在整合https证书业务后改名为Sectigo)是一家知名的数字证书提供商,拥有多种类型的数字证书,例如单域名https证书、多域名https证书、通配符https证书、IP https证书和代码签名证书等满足各类用户的…

【自然语言处理】P3 spaCy 与 NLTK(分词、词形还原与词干提取)以及 Porter 和 Snowball

目录 准备工作spaCyNLTK 文本分词spaCyNLTK 词形还原spaCyNLTK 词干提取PorterSnowball stemmers 在自然语言处理(NLP)中,文本分词是将文本拆分为单词或词组的过程,这是理解文本含义和结构的基础。Python中两个流行库——spaCy和N…

ThreadLocal父子线程传递上下文参数

自定义ThreadLocal继承InheritableThreadLocal并且实现childValue方法, 可以在子线程中也使用到主线程设置在ThreadLocal中的数据,如下所示: /*** 使用自定义MyInheritableThreadLocal实现了InheritableThreadLocal重写了childValue的目的* …

2024022期传足14场胜负前瞻

2024022期赛事由英超4场,德甲2场、意甲4场、西甲4场组成。售止时间为2月4日(周日)19点00分,敬请留意: 本期中深盘较多,1.5以下赔率3场,1.5-2.0赔率7场,其他场次是平半盘、平盘。本期…

TCP 协议的相关特性

1. TCP格式 TCP特性:有连接,全双关,面向字节流,可靠传输。(TCP安身立命的本钱,初心就是解决“可靠传输”问题) 其实TCP的特征有很多这里我就简单的介绍几个。 2. 确认应答 其实用来确保可靠性&…

mysql的hash排序和例子

哈希索引(hash index),基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每⼀⾏数据,存储引擎都会对所有的索引列计算⼀个哈希码(hash code), 哈希码是⼀个较⼩的值,并且不同键值的⾏计算出来的哈希码也不⼀样。哈希索引将…

Java并发基础:CountDownLatch全面解析!

内容概要 CountDownLatch的优点在于能够简洁高效地协调多个线程的执行顺序,确保一组线程都完成后才触发其他线程的执行,适用于资源加载、任务初始化等场景。它提供了清晰的等待/通知机制,易于理解和使用,是提升多线程程序性能和可…

HttpSession

HTTP概述 HTTP无状态协议和服务器端状态管理 HttpSession 一个用户有且最多有一个 HttpSession,并且不会被其他用户访问到。HttpSession 对象在用户第一次访问网站时自动被创建,你可以通过调用 HttpServeltRequest 的 getSession() 方法获得该对象。 …

mybatis基础操作(一)

mybatis介绍 mybatis为半自动的ORM框架 ORM:对象关系映射,将java中的一个对象与数据表中的一行记录一一对应。支持自定义sql,存储过程。 对原有jdbc进行封装,几乎消除所有jdbc代码,让开发者只需关注sql本身。 支持xml和注解配…

如何在 Mac 上重置网络设置

如何在 Mac 上重置网络设置 Mac 几乎在所有时间都非常可靠,但有时您在连接到互联网时可能会遇到困难或浏览速度缓慢。 互联网可能在您的其他设备上正常工作,这可能很烦人。 通常,问题的原因是什么并不明显,甚至根本不存在。 如果…

面试数据结构与算法总结分类+leetcode题目目录【基础版】

🧡🧡🧡算法题目总结: 这里为大家总结数据结构与算法的题库目录,如果已经解释过的题目会标注链接更新,方便查看。 数据结构概览 Array & String 大家对这两类肯定比较清楚的,同时这也是面试…

Java基础—反射

Java基础-反射 前置知识动态语言JVM堆Java引用变量类型编译时类型运行时类型举栗特殊情况 RRTI概念为什么需要RTTI例子 Class类对象前置知识类加载器概念作用 Class类对象的概念Class类对象的总结 总结一下前置知识 反射基础概念为什么要学反射我需要学到什么程度 反射的基础内…

GMT绘图笔记

(1)图框设置。在利用GMT绘制图件时,需要设置边框的类型,字体的大小,标记距离边框的距离。主要涉及的参数有: gmt set MAP_FRAME_TYPE plain/fancy 可以调整边框为火车轨道或者线段。 (2)调整图框的粗细:主要是包含有…