【C++】单例模式【两种实现方式】
目录
一、了解单例模式前的基础题
1、设计一个类,不能被拷贝
2、设计一个类,只能在堆上创建对象
3、设计一个类,只能在栈上创建对象
4、设计一个类,不能被继承
二、单例模式
1、单例模式的概念
2、单例模式的两种实现方式
2.1 懒汉模式实现单例模式
2.2 饿汉模式实现单例模式
一、了解单例模式前的基础题
1、设计一个类,不能被拷贝
拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载,因此 想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可 。
将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。class CopyBan {// ...private:CopyBan(const CopyBan&);CopyBan& operator=(const CopyBan&);//... };
原因:1. 设置成私有:如果只声明没有设置成 private ,用户自己如果在类外定义了,就可以不能禁止拷贝了2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
C++11 扩展 delete 的用法, delete 除了释放 new 申请的资源外,如果在默认成员函数后跟上=delete ,表示让编译器删除掉该默认成员函数。class CopyBan {// ...CopyBan(const CopyBan&)=delete;CopyBan& operator=(const CopyBan&)=delete;//... };
2、设计一个类,只能在堆上创建对象
思路:
创建对象一定调用构造函数(或拷贝构造:两种禁用方式),故先禁掉构造函数(私有化构造函数,对于拷贝构造下面有两种方式),则放在类中的私有下,但是怎么在堆上创建对象?用一个成员函数GetObj来在堆上创建对象(因为类内能访问私有成员中的构造函数,类外不可以),那为什么要用static修饰GetObj?这样就可以用类名::GetObj来访问了,而不用创建一个对象才能访问GetObj(因为你调用GetObj之前创建的对象一定是在栈上的),此外拷贝构造要设置为私有或直接=delete,即不能使用,因为存在你拷贝构造一个栈上的对象的场景(即用拷贝构造来创建对象)
1. 将类的构造函数私有,拷贝构造声明为delete,防止别人调用拷贝在栈上生成对象。2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
class HeapOnly
{
public:static HeapOnly* GetObj(){//专门用来在堆上创建对象return new HeapOnly;//注意这里是返回HeapOnly指针,那只是指针的拷贝,//而不是对象的拷贝,故不会调用构造函数}//C++11:防拷贝:拷贝构造函数声明成deleteHeapOnly(const HeapOnly&) = delete;
private://构造函数私有化HeapOnly(){}//C++98防拷贝:拷贝构造函数声明为私有//HeapOnly(const HeapOnly&);
};int main()
{//HeapOnly hp; //在栈上创建,失败HeapOnly* p = HeapOnly::GetObj();//成功【创建一个在堆上的对象】std::shared_ptr<HeapOnly> sp1(HeapOnly::GetObj());std::shared_ptr<HeapOnly> sp2(HeapOnly::GetObj());//HeapOnly copy(*sp1);//用栈上的对象来拷贝构造copy是不行的,故要禁掉拷贝构造return 0;
}
3、设计一个类,只能在栈上创建对象
和只能在堆上创建对象思路的唯一区别在于:创建的栈的对象需传值返回,对象的拷贝要调用拷贝构造函数,所以不能禁掉拷贝构造函数
class StackOnly
{
public:static StackOnly CreateObj(){//因为返回个匿名对象,传值返回,会调用拷贝构造//故不能禁掉拷贝构造return StackOnly();}private:StackOnly(){}
};int main()
{StackOnly obj = StackOnly::CreateObj();//StackOnly* ptr3 = new StackOnly; //失败
}
下面是有缺陷的代码:
下面的代码只是禁掉了在堆区创建数据,但是在静态区创建的数据还是无法阻止
//该方案存在一定程度缺陷,无法阻止在数据段(静态区)创建对象
class StackOnly
{
public:// 禁掉operator new可以把下面用new 调用拷贝构造申请对象给禁掉void* operator new(size_t size) = delete;
};int main()
{StackOnly so;//new分为operator new + 构造函数//StackOnly* ptr3 = new StackOnly(obj); //失败static StackOnly sso;//在静态区上开辟成功return 0;
}
4、设计一个类,不能被继承
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承 class NonInherit { public:static NonInherit GetInstance(){return NonInherit();} private:NonInherit(){} };
final 关键字, final 修饰类,表示该类不能被继承。class A final {// .... };
二、单例模式
1、单例模式的概念
设计模式:设计模式( Design Pattern )是一套 被反复使用、多数人知晓的、经过分类的、代码设计经验的 总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有 套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。单例模式:一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。![]()
单例模式有两种实现模式:懒汉模式和饿汉模式
2、单例模式的两种实现方式
设计一个类,只能创建一个对象(单例模式)
那么题目的意思就是保证全局只有一个实例对象
①、单例模式大体结构(有缺陷)
class Singleton
{
public:static Singleton* GetInstance(){if (_pinst == nullptr){//因为是静态成员变量,除了第一次为nullptr//再进来不是nullptr了,直接返回_pinst即可_pinst = new Singleton;}return _pinst;}private:Singleton(){}Singleton(const Singleton& s) = delete;static Singleton* _pinst;//静态成员的声明
};Singleton* Singleton::_pinst = nullptr;//静态成员的定义int main()
{//Singleton s1; //失败//保证获取的对象每次都是同一个cout << Singleton::GetInstance() << endl;cout << Singleton::GetInstance() << endl;cout << Singleton::GetInstance() << endl;//Singleton copy(*Singleton::GetInstance());//无法调用拷贝构造return 0;
}
运行结果:
上面代码缺陷是线程安全问题:如果两个线程同时要new一个对象(即_pinst = new Singleton),这时就发生错误了,我们要的是只有一个对象,故引出锁解决
那我们先看看这种错误出现的场景:
为了防止线程跑太快而达不到我们想看到错误情况的效果,我们用sleep睡眠来辅助
为解决上面的问题,我们用锁
2.1 懒汉模式实现单例模式
①、错误代码1
//懒汉模式:第一次获取对象时,再创建对象
class Singleton
{
public:static Singleton* GetInstance(){_mtx.lock();if (_pinst == nullptr){//因为是静态成员变量,除了第一次为nullptr//再进来不是nullptr了,直接返回_pinst即可_pinst = new Singleton;}_mtx.unlock();return _pinst;}Singleton(const Singleton& s) = delete;private:Singleton(){}static Singleton* _pinst;//静态成员的声明static mutex _mtx;
};Singleton* Singleton::_pinst = nullptr;//静态成员的定义
mutex Singleton::_mtx;
上面代码意义是创建对象保证只有一个线程在访问,解决了不会同时创建对象的问题,但是如果new失败了要抛异常怎么办?此时正在访问的线程都没有解锁,其他线程也无法访问了,故要用unique_lock:也会帮你锁,且不管你是否主动unlock解锁,都会在出了作用域后解锁
②、用unique_lock来改进
③、只需第一次加锁
只要_pinst指向已经new出来的实例对象,就无须加锁了
④、析构单例模式的对象
一般单例模式下的new出来的这个全局唯一对象是不需要释放的,因为这种单例模式下的对象,整个程序只有一个,它是一直在用的,没必要释放。
如果你就想要释放的话,两种方式:
①、静态函数
②、静态变量的生命周期
#include<vector>
#include<thread>
#include<mutex>namespace lazy_man
{//懒汉模式:第一次获取对象时,再创建对象class Singleton{public:static Singleton* GetInstance(){//_mtx.lock();unique_lock会锁,锁完之后不管你是否解锁,出了作用域他都会自动解锁而你现在就这一个地方需要锁,故再加个{}作用域//{// unique_lock<mutex> lock(_mtx);// if (_pinst == nullptr)// {//因为是静态成员变量,除了第一次为nullptr// //再进来不是nullptr了,直接返回_pinst即可// _pinst = new Singleton;// }//}//双检查:if (_pinst == nullptr){//加锁只是为了保护第一次{unique_lock<mutex> lock(_mtx);if (_pinst == nullptr){//因为是静态成员变量,除了第一次为nullptr//再进来不是nullptr了,直接返回_pinst即可_pinst = new Singleton;//只要_pinst指向已经new出来的实例对象,就无须加锁了}}}//_mtx.unlock();return _pinst;}//如果你就想释放这个对象的话,自己写个静态函数即可,手动调static void DelInstance(){unique_lock<mutex> lock(_mtx);delete _pinst;_pinst = nullptr;}Singleton(const Singleton& s) = delete;private:Singleton(){}static Singleton* _pinst;//静态成员的声明static mutex _mtx;};Singleton* Singleton::_pinst = nullptr;//静态成员的定义mutex Singleton::_mtx;//1、如果要手动释放单例对象,可以调用DelInstance//2、如果需要程序结束时,正常释放单例对象,可以加入下面的设计class GC{public:~GC(){Singleton::DelInstance();}};static GC gc;//main函数结束就会调用它的析构函数,进而释放_pinstvoid x(){Singleton s1; //失败保证获取的对象每次都是同一个//cout << Singleton::GetInstance() << endl;//cout << Singleton::GetInstance() << endl;//cout << Singleton::GetInstance() << endl;Singleton copy(*Singleton::GetInstance());//无法调用拷贝构造//代码中存在线程问题:若多个线程同时获取一个对象呢?vector<std::thread> vthreads;int n = 4;for (size_t i = 0; i < n; ++i){vthreads.push_back(std::thread([](){//cout << std::this_thread::get_id() << ":";cout << Singleton::GetInstance() << endl;}));//线程对象里面用了一个lambda表达式}for (auto& t : vthreads){t.join();}}
}int main()
{lazy_man::x();return 0;
}
运行结果:
2.2 饿汉模式实现单例模式
饿汉模式有个静态成员变量,静态变量在程序运行前创建,在程序的整个运行期间始终存在,他始终保持原先的值,除非给他赋予一个不同的值或程序结束。正因为程序前创建,那此时只有主线程,不存在线程安全问题。
namespace hungry_man
{//饿汉模式 --main函数之前就创建对象class Singleton{public:static Singleton* GetInstance(){return &_inst;}Singleton(const Singleton& s) = delete;private: Singleton(){}static Singleton _inst;};//static对象是在main函数之前创建的,这时只有主线程,故不存在线程安全问题Singleton Singleton::_inst; void x(){Singleton s1; //失败保证获取的对象每次都是同一个//cout << Singleton::GetInstance() << endl;//cout << Singleton::GetInstance() << endl;//cout << Singleton::GetInstance() << endl;Singleton copy(*Singleton::GetInstance());//无法调用拷贝构造//代码中存在线程问题:若多个线程同时获取一个对象呢?vector<std::thread> vthreads;int n = 4;for (size_t i = 0; i < n; ++i){vthreads.push_back(std::thread([](){//cout << std::this_thread::get_id() << ":";cout << Singleton::GetInstance() << endl;}));//线程对象里面用了一个lambda表达式}for (auto& t : vthreads){t.join();}}
}int main()
{hungry_man::x();return 0;
}
2.3懒汉和饿汉模式的区别
相关文章:

【C++】单例模式【两种实现方式】
目录 一、了解单例模式前的基础题 1、设计一个类,不能被拷贝 2、设计一个类,只能在堆上创建对象 3、设计一个类,只能在栈上创建对象 4、设计一个类,不能被继承 二、单例模式 1、单例模式的概念 2、单例模式的两种实现方式 …...

php的api接口token简单实现
<?php // 生成 Token function generateToken() {$token bin2hex(random_bytes(16)); // 使用随机字节生成 tokenreturn $token; } // 存储 Token(这里使用一个全局变量来模拟存储) $tokens []; // 验证 Token function validateToken($token) {gl…...

CCNA课程实验-13-PPPoE
目录 实验条件网络拓朴需求 配置实现基础配置模拟运营商ISP配置ISP的DNS配置出口路由器OR基础配置PC1基础配置 出口路由器OR配置PPPOE拨号创建NAT(PAT端口复用) PC1测试结果 实验条件 网络拓朴 需求 OR使用PPPoE的方式向ISP发送拨号的用户名和密码,用户名…...

cocosCreator 之 Bundle使用
版本: v3.4.0 语言: TypeScript 环境: Mac Bundle简介 全名 Asset Bundle(简称AB包),自cocosCreator v2.4开始支持,用于作为资源模块化工具。 允许开发者根据项目需求将贴图、脚本、场景等资源划分在 Bundle 中&am…...

分类网络搭建示例
搭建CNN网络 本章我们来学习一下如何搭建网络,初始化方法,模型的保存,预训练模型的加载方法。本专栏需要搭建的是对分类性能的测试,所以这里我们只以VGG为例。 请注意,这里定义的只是一个简陋的版本,后续一…...
为 Ubuntu 虚拟机构建 SSH 服务器
以校园网环境和VMware为例,关键步骤如下: 安装 SSH 服务: 打开 Ubuntu 虚拟机。打开终端。输入命令 sudo apt-get update 更新软件包列表。输入命令 sudo apt-get install openssh-server 安装 SSH 服务。 配置 SSH 服务: 编辑配…...

SpringBoot--中间件技术-2:整合redis,redis实战小案例,springboot cache,cache简化redis的实现,含代码
SpringBoot整合Redis 实现步骤 导pom文件坐标 <!--redis依赖--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency>yaml主配置文件,配置…...
linux rsyslog配置文件详解
1.rsyslog配置文件简介 linux rsyslog配置文件/etc/rsyslog.conf分为三部分:MODULES、GLOBAL DIRECTIVES、RULES ryslog模块说明 模块说明MODULES指定接收日志的协议和端口。若要配置日志服务器,则需要将相应的配置项注释去掉。GLOBAL DIRECTIVES主要用来配置日志模版。指定…...

wordpress是什么?快速搭网站经验分享
作者主页 📚lovewold少个r博客主页 ⚠️本文重点:c入门第一个程序和基本知识讲解 👉【C-C入门系列专栏】:博客文章专栏传送门 😄每日一言:宁静是一片强大而治愈的神奇海洋! 目录 前言 wordp…...

排序 算法(第4版)
本博客参考算法(第4版):算法(第4版) - LeetBook - 力扣(LeetCode)全球极客挚爱的技术成长平台 本文用Java实现相关算法。 我们关注的主要对象是重新排列数组元素的算法,其中每个元素…...

asp.net 在线音乐网站系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio
一、源码特点 asp.net 在线音乐网站系统是一套完善的web设计管理系统,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为vs2010,数据库为sqlserver2008,使用c#语言 开发 asp.net 在线音乐网站系统1 应用…...
ElastaticSearch -- es之Filters aggregation 先过滤再聚合
使用场景 使用es时,有时我们需要先过滤后再聚合,但如果直接在query的filter中过滤,不止会影响到一个聚合,还会影响到其他的聚合结果。 比如,我们想要统计深圳市某个品牌的总销售额,以及该品牌的女款衣服的…...
如何把一个接口设计好?
如何把一个接口设计好? 如何设计一个接口?是在我们日常开发或者面试时经常问及的一个话题。很多人觉得这不就是CRUD,能实现不就行了。单纯实现来说,并非难事,但要做到易用、易扩展、易维护并不是一件简单的事。这里并…...

mini-vue 的设计
mini-vue 的设计 mini-vue 使用流程与结果预览: <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name&qu…...
React整理杂记(一)
1.React三项依赖 1.react.js -> 核心代码 2.react-dom.js -> 渲染成dom 3.babel.js->非必须,将jsx转为js 类组件中直接定义的方法,都属于严格模式下 this的绑定可以放到constructor(){}中 2. JSX语法 1.可以直接插入的元素: num…...
[100天算法】-统计封闭岛屿的数目(day 74)
题目描述 有一个二维矩阵 grid ,每个位置要么是陆地(记号为 0 )要么是水域(记号为 1 )。我们从一块陆地出发,每次可以往上下左右 4 个方向相邻区域走,能走到的所有陆地区域,我们将其…...
esp32-rust-std-examples-blinky
以下为在 ESP-IDF (FreeRTOS) 上运行的 blinky 示例: https://github.com/esp-rs/esp-idf-hal/blob/master/examples/blinky.rs //! Blinks an LED //! //! This assumes that a LED is connected to GPIO4. //! Depending on your target and the board you are …...
【docker容器技术与K8s】
【docker容器技术与K8s】 一、Docker容器技术 1、Docker的学习路线 (1)学习Docker基本命令(容器管理和镜像管理) (2)学习使用Docker搭建常用软件 (3)学习Docker网络模式 启动容器的…...

RT-DTER 引入用于低分辨率图像和小物体的新 CNN 模块 SPD-Conv
论文地址:https://arxiv.org/pdf/2208.03641v1.pdf 代码地址:https://github.com/labsaint/spd-conv 卷积神经网络(CNN)在图像分类、目标检测等计算机视觉任务中取得了巨大的成功。然而,在图像分辨率较低或对象较小的更困难的任务中,它们的性能会迅速下降。 这源于现有CNN…...
Folw + Room 实现自动观察数据库的刷新
1、Room :定义数据结构、创建数据库 // 定义实体 Entity data class TestModel ()// 定义数据库 Dao interface TestDao { Query("SELECT * FROM TestTable") fun getAll(): List<TestModel> }// 获取数据库 abstract class TestDatabase: RoomDat…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...

windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能
指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...
ThreadLocal 源码
ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物,因为每个访问一个线程局部变量的线程(通过其 get 或 set 方法)都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段,这些类希望将…...

spring boot使用HttpServletResponse实现sse后端流式输出消息
1.以前只是看过SSE的相关文章,没有具体实践,这次接入AI大模型使用到了流式输出,涉及到给前端流式返回,所以记录一下。 2.resp要设置为text/event-stream resp.setContentType("text/event-stream"); resp.setCharacter…...

PLC入门【4】基本指令2(SET RST)
04 基本指令2 PLC编程第四课基本指令(2) 1、运用上接课所学的基本指令完成个简单的实例编程。 2、学习SET--置位指令 3、RST--复位指令 打开软件(FX-TRN-BEG-C),从 文件 - 主画面,“B: 让我们学习基本的”- “B-3.控制优先程序”。 点击“梯形图编辑”…...