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

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

【Python】 -- 趣味代码 - 小恐龙游戏

文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI

前一阵子在百度 AI 开发者大会上&#xff0c;看到基于小智 AI DIY 玩具的演示&#xff0c;感觉有点意思&#xff0c;想着自己也来试试。 如果只是想烧录现成的固件&#xff0c;乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外&#xff0c;还提供了基于网页版的 ESP LA…...

《基于Apache Flink的流处理》笔记

思维导图 1-3 章 4-7章 8-11 章 参考资料 源码&#xff1a; https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

如何在最短时间内提升打ctf(web)的水平?

刚刚刷完2遍 bugku 的 web 题&#xff0c;前来答题。 每个人对刷题理解是不同&#xff0c;有的人是看了writeup就等于刷了&#xff0c;有的人是收藏了writeup就等于刷了&#xff0c;有的人是跟着writeup做了一遍就等于刷了&#xff0c;还有的人是独立思考做了一遍就等于刷了。…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...

C++:多态机制详解

目录 一. 多态的概念 1.静态多态&#xff08;编译时多态&#xff09; 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1&#xff09;.协变 2&#xff09;.析构函数的重写 5.override 和 final关键字 1&#…...

LRU 缓存机制详解与实现(Java版) + 力扣解决

&#x1f4cc; LRU 缓存机制详解与实现&#xff08;Java版&#xff09; 一、&#x1f4d6; 问题背景 在日常开发中&#xff0c;我们经常会使用 缓存&#xff08;Cache&#xff09; 来提升性能。但由于内存有限&#xff0c;缓存不可能无限增长&#xff0c;于是需要策略决定&am…...

论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving

地址&#xff1a;LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂&#xff0c;正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...

用鸿蒙HarmonyOS5实现中国象棋小游戏的过程

下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...