【C++之单例模式】

C++之单例模式

  • 前言
  • 介绍
    • 1、单例模式是什么?
      • 1.1 实现单例模式的三个要点
      • 1.2 单例模式分类
    • 2. 懒汉式
      • 2.1 懒汉实现:基础方法
      • 2.2 懒汉实现:基于单锁
      • 2.3 懒汉实现:基于双重检测锁
      • 2.4 懒汉实现:基于双重检测锁和资源管理
        • 2.4.1 智能指针方式
        • 2.4.2 静态嵌套类方式
      • 2.5 懒汉实现:基于局部静态对象
    • 3. 饿汉式
      • 3.1 饿汉实现:基础方法
    • 4. 总结

前言

单例模式(Singleton Pattern)是 面向对象中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

优点:通过单例模式的设计,使得创建的类在当前进程中只有一个实例,并提供一个全局性的访问点,这样可以规避因频繁创建对象而导致的 内存飙升 情况。

介绍

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

优点:

1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

1、单例模式是什么?

在面向对象编程中,有时候我们希望一个类只有一个实例化的对象,比如线程池,缓存等。这些类有且只有一个唯一的实例,这种设计模式被称为单例模式。

1.1 实现单例模式的三个要点

1)私有化构造函数:这样外界就无法自由地创建类对象,进而阻止了多个实例的产生。
2)类定义中含有该类的唯一静态私有对象:静态变量存放在全局存储区,且是唯一的,供所有对象使用。
3)用公有的静态函数来获取该实例:提供了访问接口。

1.2 单例模式分类

单例模式有两种主要实现方法:懒汉模式和饿汉模式。

  1. 懒汉模式特点是当外界调用时才进行实例化;
  2. 饿汉模式特点是一开始就对实例进行初始化,调用时直接返回这个构建好的实例。

2. 懒汉式

懒汉模式特点是当外界调用时才进行实例化。

2.1 懒汉实现:基础方法

是否多线程安全:否
实现难度:易
描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
缺点:一个是线程安全,另一个是内存泄漏。
线程安全是因为在多线程场景下,有可能出现多个线程同时进行new操作的情况,没通过加锁来限制。
内存泄漏是因为使用了new在堆上分配了资源,那么在程序结束时,也应该进行delete,确保堆中数据释放。

public class Singleton {  // 静态私有对象private static Singleton instance;  // 私有构造函数private Singleton (){}  // 公有接口获取唯一实例public static Singleton getInstance() {  if (instance == null) {  instance = new Singleton();  }  return instance;  }  
}

这种方式在单线程下没有问题,但是如果多线程模式下,当唯一实例还没有创建,两个线程同时调用getinstance就可能同时创建对象,导致错误。

2.2 懒汉实现:基于单锁

是否多线程安全:是
实现难度:较易
描述:这种方式采用单锁机制,有可能造成阻塞。

Singleton*Singleton::getInstance(){m.lock();if (_instance == nullptr)_instance = new Singleton;m.unlock();return _instance;
}

加锁又会带来另外的性能问题,如果每个线程每次获取实例都加锁,有可能造成阻塞的发生。实际上,上锁的目的是为了防止有多个线程在实例未被初始化的情况下,同时对他进行初始化,如果实例已经被创建了,就不需要考虑这个问题了,所以就可以采用二次加锁的方法来提高程序的性能。

2.3 懒汉实现:基于双重检测锁

是否多线程安全:是
实现难度:较复杂
描述:这种方式采用双锁机制,可以确保线程安全,且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。

Singleton*Singleton::getInstance(){if (_instance == nullptr){m.lock();if (_instance == nullptr){_instance = new Singleton;}m.unlock();}return _instance;
}

接下来,我们再解决内存泄漏(资源释放)问题,对懒汉式实现进行进一步的改进。

2.4 懒汉实现:基于双重检测锁和资源管理

是否多线程安全:是
实现难度:较复杂
描述:这种方式采用双锁机制,可以确保线程安全,且在多线程情况下能保持高性能。并且加入资源管理机制,以达到对资源的释放的目的。
我们加入资源管理机制,以达到对资源的释放的目的,解决方法有两个:智能指针&静态嵌套类。

2.4.1 智能指针方式

将实例指针更换为智能指针,另外智能指针在初始化时,还需要人为添加公有的毁灭函数,因为析构函数私有化了。

#include <iostream>
#include <mutex>
using namespace std;// 单例模式演示类
class Singleton
{
public:
// 公有接口获取唯一实例static shared_ptr<Singleton> getInstance() {
// 若为空则创建if (instance == nullptr) {
// 加锁保证线程安全
// 如果两个线程同时进行到这一步,一个线程继续向下执行时,另一个线程被堵塞
// 等锁解除后,被堵塞的线程就会跳过下面的if了,因为此时实例已经构建完毕lock_guard<mutex> l(m_mutex);if (instance == nullptr) {cout << "实例为空,开始创建。" << endl;instance.reset(new Singleton(), destoryInstance);cout << "地址为:" << instance << endl;cout << "创建结束。" << endl;}}else {cout << "已有实例,返回。" << endl;}return instance;
}
// 毁灭实例
static void destoryInstance(Singleton* x) {cout << "自定义释放实例" << endl;delete x;
}private:
// 私有构造函数
Singleton() {cout << "构造函数启动。" << endl;
};// 私有析构函数
~Singleton() {cout << "析构函数启动。" << endl;
};private:
// 静态私有对象
static shared_ptr<Singleton> instance;
// 锁
static mutex m_mutex;
};// 初始化
shared_ptr<Singleton> Singleton::instance;
mutex Singleton::m_mutex;

应用智能指针后,在程序结束时,它自动进行资源的释放,解决了内存泄漏的问题。

2.4.2 静态嵌套类方式

类中定义一个嵌套类,初始化该类的静态对象,当程序结束时,该对象进行析构的同时,将单例实例也删除了。

#include <iostream>
#include <mutex>
using namespace std;// 单例模式演示类
class Singleton
{
public:
// 公有接口获取唯一实例
static Singleton* getInstance() {
// 若为空则创建if (instance == nullptr) {
// 加锁保证线程安全
// 如果两个线程同时进行到这一步,一个线程继续向下执行时,另一个线程被堵塞
// 等锁解除后,被堵塞的线程就会跳过下面的if了,因为此时实例已经构建完毕lock_guard<mutex> l(m_mutex);if (instance == nullptr) {cout << "实例为空,开始创建。" << endl;instance = new Singleton();cout << "地址为:" << instance << endl;cout << "创建结束。" << endl;}}else {cout << "已有实例,返回。" << endl;}return instance;
}private:
// 私有构造函数
Singleton() {cout << "构造函数启动。" << endl;};// 私有析构函数
~Singleton() {
cout << "析构函数启动。" << endl;
};// 定义一个删除器
class Deleter {
public:
Deleter() {};
~Deleter() {
if (instance != nullptr) {cout << "删除器启动。" << endl;delete instance;instance = nullptr;}
}
};// 删除器是嵌套类,当该静态对象销毁的时候,也会将单例实例销毁
static Deleter m_deleter;
private:
// 静态私有对象
static Singleton* instance;
// 锁
static mutex m_mutex;
};// 初始化
Singleton* Singleton::instance = nullptr;
mutex Singleton::m_mutex;
Singleton::Deleter Singleton::m_deleter;

2.5 懒汉实现:基于局部静态对象

是否多线程安全:是
实现难度:一般
描述:C++11后,规定了局部静态对象在多线程场景下的初始化行为,只有在首次访问时才会创建实例,后续不再创建而是获取。若未创建成功,其他的线程在进行到这步时会自动等待。注意C++11前的版本不是这样的。

因为有上述的改动,所以出现了一种更简洁方便优雅的实现方法,基于局部静态对象实现。

#include <iostream>
#include <mutex>
using namespace std;// 单例模式演示类
class Singleton
{
public:
// 公有接口获取唯一实例
static Singleton& getInstance() {cout << "获取实例" << endl;static Singleton instance;cout << "地址为:" << &instance << endl;return instance;}
private:
// 私有构造函数
Singleton() {cout << "构造函数启动。" << endl;};// 私有析构函数
~Singleton() {cout << "析构函数启动。" << endl;};
};

3. 饿汉式

饿汉模式特点是一开始就对实例进行初始化,调用时直接返回这个构建好的实例。

3.1 饿汉实现:基础方法

是否多线程安全:是
实现难度:易
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。第一次调用才初始化,避免内存浪费。
缺点:类加载时就初始化,浪费内存。必须加锁 synchronized 才能保证单例,但加锁会影响效率。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

#include <iostream>
#include <mutex>
using namespace std;// 单例模式演示类
class Singleton
{
public:
// 公有接口获取唯一实例
static Singleton* getInstance() {cout << "获取实例" << endl;cout << "地址为:" << instance << endl;return instance;}
private:
// 私有构造函数
Singleton() {cout << "构造函数启动。" << endl;
};// 私有析构函数
~Singleton() {cout << "析构函数启动。" << endl;
};private:
// 静态私有对象
static Singleton* instance;
};// 初始化
Singleton* Singleton::instance = new Singleton();

main还没开始,实例就已经构建完毕,获取实例的函数也不需要进行判空操作,因此也就不用双重检测锁来保证线程安全了,它本身已经是线程安全状态了。
但是内存泄漏的问题还是要解决的,这点同懒汉是一样的。可以通过智能指针和静态嵌套实现。

4. 总结

一般情况下,建议使用基于双重检测锁和资源管理搭配智能指针的懒汉方式。

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

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

相关文章

ssm基于Vue的戏剧推广网站论文

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统戏剧推广信息管理难度大&#xff0c;容错率低&#xff0c…

代码随想录day23 二叉岁终章

669. 修剪二叉搜索树 题目 给定一个二叉搜索树&#xff0c;同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树&#xff0c;使得所有节点的值在[L, R]中 (R>L) 。你可能需要改变树的根节点&#xff0c;所以结果应当返回修剪好的二叉搜索树的新的根节点。 思考 这题有个…

2024年中职网络安全——Windows操作系统渗透测试(Server2105)

Windows操作系统渗透测试 任务环境说明&#xff1a; 服务器场景&#xff1a;Server2105服务器场景操作系统&#xff1a;Windows&#xff08;版本不详&#xff09;&#xff08;封闭靶机&#xff09;需要环境加Q 目录 1.通过本地PC中渗透测试平台Kali对服务器场景进行系统服务…

Docker 部署后端项目自动化脚本

文章目录 开机自启动docker打包后端项目Dockerfile文件脚本文件使用 开机自启动docker systemctl enable dockersystemctl is-enabled docker打包后端项目 这里的项目位置是target同级目录 1.在项目下面新建一个bin目录 新建一个package.txt 写入下方代码后 后缀改为.bat ec…

配置git服务器

第一步&#xff1a; jdk环境配置 &#xff08;1&#xff09;搜索【高级系统设置】&#xff0c;选择【高级】选项卡&#xff0c;点【环境变量】 &#xff08;2&#xff09;在【系统变量】里面&#xff0c;点击【新建】 &#xff08;3&#xff09;添加JAVA_HOME环境变量JAVA_HO…

展开运算符(Spread Operator)

展开运算符&#xff08;Spread Operator&#xff09;是ES6中引入的一种语法&#xff0c;使用三个连续的点&#xff08;...&#xff09;表示。它可以在多种场合下使用&#xff0c;主要用途是“展开”数组或对象中的元素或属性。 使用展开运算符的几种常见情景&#xff1a; 在函…

小程序学习基础(页面加载)

打开首页&#xff0c;然后点击第一个按钮进去心得页面 进入心得页面以后 第一个模块是轮播图用的是swiper组件&#xff0c;然后就是四个按钮绑定点击事件&#xff0c;最后就是下拉刷新&#xff0c;下拉滚动&#xff0c;上拉加载。代码顺序wxml,js,wcss,json。 <!--pages/o…

【开源商城推荐-LGPL-3.0】ts-mall 聚惠星商城

dts-shop: 聚惠星商城 DTS-SHOP&#xff0c;基于 微信小程序 springboot vue 技术构建 &#xff0c;支持单店铺&#xff0c;多店铺入驻的商城平台。项目包含 微信小程序&#xff0c;管理后台。基于java后台语言&#xff0c;已功能闭环&#xff0c;且达到商用标准的一套项目体…

金和OA C6 MailTemplates.aspx SQL注入漏洞

文章目录 产品简介漏洞概述指纹识别漏洞利用修复建议 产品简介 金和OA协同办公管理系统软件&#xff08;简称金和OA&#xff09;&#xff0c;本着简单、适用、高效的原则&#xff0c;贴合企事业单位的实际需求&#xff0c;实行通用化、标准化、智能化、人性化的产品设计&#…

Day05

19.用户与权限管理 19.1 用户管理 MySQL用户可以分为普通用户和root用户。root用户是超级管理员&#xff0c;拥有所有权限&#xff0c;包括创建用户、删除用户和修改用户的密码等管理权限&#xff1b;普通用户只拥有被授予的各种权限。 MySQL提供了许多语句用来管理用户账号…

kotlin的注解

在Kotlin中&#xff0c;注解&#xff08;Annotations&#xff09;是一种用于在程序代码中添加元数据的特殊标记。它们提供了对代码的描述性信息&#xff0c;但本身并不会影响程序的运行。注解可以应用于类、方法、属性等程序元素上&#xff0c;用于提供关于这些元素的额外信息。…

Linux自动化部署脚本

1:最近项目部署比较频繁终于熬不住了 就有下面的这东西 #!/bin/sh #报错停止运行 set -e # 获取tomcat的PID TOMCAT_PID$(ps -ef | grep tomcat | grep -v grep | awk {print $2}) # tomcat的启动文件位置 START_TOMCAT/mnt/tomcat/bin/startup.sh # 项目文件部署位置 PROJECT…

php 的数据类型

目录 1.整型 2.浮点型 3.布尔类型 4.字符串 5.数组 6.NULL 7.对象 8.资源类型 查看变量对应值的类型&#xff1a; 1.使用“gettype(传入一个变量var)”来显示变量var的类型; 只会显示类型 2.使用“var_dump(传入一个变量var)”来显示变量var的类型; 会显示具体内容打…

5 - 视图|存储过程

视图&#xff5c;存储过程 视图视图基本使用使用视图视图进阶 存储过程创建存储过程存储过程进阶存储过程参数循环结构 视图 视图是虚拟存在的表 表头下的数据在真表里 表头下的数据存储在创建视图时 在select命令访问的真表里 优点&#xff1a; 安全数据独立简单 用户无需关…

git常用指令及应用案例

一、常用指令 用户配置 git config --global user.name "bettyaner" git config --global user.email bettyaner163.com工作常用指令 // 初始化仓库 git init // 对状态的跟踪、分为内容状态和文件状态 // 内容状态标示内容文件的改变&#xff0c;有三个区域&…

ADS仿真 之 容差/良率分析

之所以要进行容差分析&#xff0c; 是因为任何电子元器件均存在一定的误差&#xff0c; 如电感、电容的精度等。 例如一个标称为2.0nH0.1nH的电感&#xff0c;代表的意思产品有99.74%的概率落在2.0nH0.1nH范围内&#xff0c; 即满足6σ &#xff0c;σ是标准偏差或者说方差&…

Hyperledger Fabric 二进制安装部署 Peer 节点

规划网络拓扑 3 个 orderer 节点&#xff1b;组织 org1 , org1 下有两个 peer 节点&#xff0c; peer0 和 peer1; 组织 org2 , org2 下有两个 peer 节点&#xff0c; peer0 和 peer1; 节点宿主机 IPhosts端口cli192.168.1.66N/AN/Aorderer0192.168.1.66orderer0.example.com70…

案例分享:当前高端低延迟视频类产品方案分享(内窥镜、记录仪、车载记录仪、车载环拼、车载后视镜等产品)

若该文为原创文章&#xff0c;转载请注明出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/135439369 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结…

大模型学习

大模型的参数量和显存占用估算 现在业界的大语言模型都是基于transformer模型的&#xff0c;模型结构主要有两大类&#xff1a;encoder-decoder&#xff08;代表模型是T5&#xff09;和decoder-only&#xff0c;具体的&#xff0c;decoder-only结构又可以分为Causal LM&#x…

鸿蒙系统应用开发之开发准备

今天我们来聊一聊鸿蒙系统应用开发之前&#xff0c;要做什么准备工作&#xff0c;如下图所示&#xff0c;我们要做的就是安装DevEco Studio&#xff0c;然后配置开发环境。 老规矩&#xff0c;拍拍手&#x1f44f;&#xff0c;上菜。 安装DevEco Studio 首先我们打开链接HUAWEI…