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

【C++】单例模式【两种实现方式】

 

目录

 一、了解单例模式前的基础题

1、设计一个类,不能被拷贝

2、设计一个类,只能在堆上创建对象

3、设计一个类,只能在栈上创建对象

4、设计一个类,不能被继承

二、单例模式

1、单例模式的概念

2、单例模式的两种实现方式 

2.1 懒汉模式实现单例模式

2.2 饿汉模式实现单例模式


 一、了解单例模式前的基础题

1、设计一个类,不能被拷贝

拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载,因此 想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可
C++98
将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
class CopyBan
{// ...private:CopyBan(const CopyBan&);CopyBan& operator=(const CopyBan&);//...
};
原因:
1. 设置成私有:如果只声明没有设置成 private ,用户自己如果在类外定义了,就可以不
能禁止拷贝了
2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写
反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
C++11
 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方式
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit(){}
};
C++11方式
 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、设计一个类&#xff0c;不能被拷贝 2、设计一个类&#xff0c;只能在堆上创建对象 3、设计一个类&#xff0c;只能在栈上创建对象 4、设计一个类&#xff0c;不能被继承 二、单例模式 1、单例模式的概念 2、单例模式的两种实现方式 …...

php的api接口token简单实现

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

CCNA课程实验-13-PPPoE

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

cocosCreator 之 Bundle使用

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

分类网络搭建示例

搭建CNN网络 本章我们来学习一下如何搭建网络&#xff0c;初始化方法&#xff0c;模型的保存&#xff0c;预训练模型的加载方法。本专栏需要搭建的是对分类性能的测试&#xff0c;所以这里我们只以VGG为例。 请注意&#xff0c;这里定义的只是一个简陋的版本&#xff0c;后续一…...

为 Ubuntu 虚拟机构建 SSH 服务器

以校园网环境和VMware为例&#xff0c;关键步骤如下&#xff1a; 安装 SSH 服务&#xff1a; 打开 Ubuntu 虚拟机。打开终端。输入命令 sudo apt-get update 更新软件包列表。输入命令 sudo apt-get install openssh-server 安装 SSH 服务。 配置 SSH 服务&#xff1a; 编辑配…...

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主配置文件&#xff0c;配置…...

linux rsyslog配置文件详解

1.rsyslog配置文件简介 linux rsyslog配置文件/etc/rsyslog.conf分为三部分:MODULES、GLOBAL DIRECTIVES、RULES ryslog模块说明 模块说明MODULES指定接收日志的协议和端口。若要配置日志服务器,则需要将相应的配置项注释去掉。GLOBAL DIRECTIVES主要用来配置日志模版。指定…...

wordpress是什么?快速搭网站经验分享

​作者主页 &#x1f4da;lovewold少个r博客主页 ⚠️本文重点&#xff1a;c入门第一个程序和基本知识讲解 &#x1f449;【C-C入门系列专栏】&#xff1a;博客文章专栏传送门 &#x1f604;每日一言&#xff1a;宁静是一片强大而治愈的神奇海洋&#xff01; 目录 前言 wordp…...

排序 算法(第4版)

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

asp.net 在线音乐网站系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net 在线音乐网站系统是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c#语言 开发 asp.net 在线音乐网站系统1 应用…...

ElastaticSearch -- es之Filters aggregation 先过滤再聚合

使用场景 使用es时&#xff0c;有时我们需要先过滤后再聚合&#xff0c;但如果直接在query的filter中过滤&#xff0c;不止会影响到一个聚合&#xff0c;还会影响到其他的聚合结果。 比如&#xff0c;我们想要统计深圳市某个品牌的总销售额&#xff0c;以及该品牌的女款衣服的…...

如何把一个接口设计好?

如何把一个接口设计好&#xff1f; 如何设计一个接口&#xff1f;是在我们日常开发或者面试时经常问及的一个话题。很多人觉得这不就是CRUD&#xff0c;能实现不就行了。单纯实现来说&#xff0c;并非难事&#xff0c;但要做到易用、易扩展、易维护并不是一件简单的事。这里并…...

mini-vue 的设计

mini-vue 的设计 mini-vue 使用流程与结果预览&#xff1a; <!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->非必须&#xff0c;将jsx转为js 类组件中直接定义的方法&#xff0c;都属于严格模式下 this的绑定可以放到constructor(){}中 2. JSX语法 1.可以直接插入的元素&#xff1a; num…...

[100天算法】-统计封闭岛屿的数目(day 74)

题目描述 有一个二维矩阵 grid &#xff0c;每个位置要么是陆地&#xff08;记号为 0 &#xff09;要么是水域&#xff08;记号为 1 &#xff09;。我们从一块陆地出发&#xff0c;每次可以往上下左右 4 个方向相邻区域走&#xff0c;能走到的所有陆地区域&#xff0c;我们将其…...

esp32-rust-std-examples-blinky

以下为在 ESP-IDF (FreeRTOS) 上运行的 blinky 示例&#xff1a; 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的学习路线 &#xff08;1&#xff09;学习Docker基本命令&#xff08;容器管理和镜像管理&#xff09; &#xff08;2&#xff09;学习使用Docker搭建常用软件 &#xff08;3&#xff09;学习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 &#xff1a;定义数据结构、创建数据库 // 定义实体 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征文&#xff5c;DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色&#xff0c;华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型&#xff0c;能助力我们轻松驾驭 DeepSeek-V3/R1&#xff0c;本文中将分享如何…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”

2025年#高考 将在近日拉开帷幕&#xff0c;#AI 监考一度冲上热搜。当AI深度融入高考&#xff0c;#时间同步 不再是辅助功能&#xff0c;而是决定AI监考系统成败的“生命线”。 AI亮相2025高考&#xff0c;40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕&#xff0c;江西、…...

Java毕业设计:WML信息查询与后端信息发布系统开发

JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发&#xff0c;实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构&#xff0c;服务器端使用Java Servlet处理请求&#xff0c;数据库采用MySQL存储信息&#xff0…...

windows系统MySQL安装文档

概览&#xff1a;本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容&#xff0c;为学习者提供全面的操作指导。关键要点包括&#xff1a; 解压 &#xff1a;下载完成后解压压缩包&#xff0c;得到MySQL 8.…...

安卓基础(Java 和 Gradle 版本)

1. 设置项目的 JDK 版本 方法1&#xff1a;通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分&#xff0c;设置 Gradle JDK 方法2&#xff1a;通过 Settings File → Settings... (或 CtrlAltS)…...

c# 局部函数 定义、功能与示例

C# 局部函数&#xff1a;定义、功能与示例 1. 定义与功能 局部函数&#xff08;Local Function&#xff09;是嵌套在另一个方法内部的私有方法&#xff0c;仅在包含它的方法内可见。 • 作用&#xff1a;封装仅用于当前方法的逻辑&#xff0c;避免污染类作用域&#xff0c;提升…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能

指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...

ThreadLocal 源码

ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物&#xff0c;因为每个访问一个线程局部变量的线程&#xff08;通过其 get 或 set 方法&#xff09;都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段&#xff0c;这些类希望将…...

spring boot使用HttpServletResponse实现sse后端流式输出消息

1.以前只是看过SSE的相关文章&#xff0c;没有具体实践&#xff0c;这次接入AI大模型使用到了流式输出&#xff0c;涉及到给前端流式返回&#xff0c;所以记录一下。 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)&#xff0c;从 文件 - 主画面&#xff0c;“B: 让我们学习基本的”- “B-3.控制优先程序”。 点击“梯形图编辑”…...