【C++】 单例设计模式的讲解

前言
在我们的学习中不免会遇到一些要设计一些特殊的类,要求这些类只能在内存中特定的位置创建对象,这就需要我们对类进行一些特殊的处理,那我们该如何解决呢?

目录

    • 1. 特殊类的设计
      • 1.1 设计一个类,不能被拷贝:
      • 1.2设计一个类,只能在堆上创建对象
        • 方法二:
      • 1.3 设计一个类,只能在栈上创建对象
      • 1.4 请设计一个类,不能被继承:
    • 2 单例模式:
      • 2.1 饿汉模式:
      • 2.2 懒汉模式:
      • 2.3 特殊情况下,单例类的释放

1. 特殊类的设计

1.1 设计一个类,不能被拷贝:

  • 拷贝构造函数以及赋值运算符重载
  • 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可

C++98的处理方式:

  • 让拷贝构造和赋值重载设成私有即可

  • 只声明不定义。

  • C++11扩展delete的用法,delete除了释放new申请的资源外

  • 如果在默认成员函数后跟上 = delete,表示让编译器删除掉该默认成员函数。

class CopyBan
{
	// ...
	CopyBan(const CopyBan&) = delete;
	CopyBan& operator=(const CopyBan&) = delete;
	// ...
};

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

C++98的设计的方法:

  • 就直接将构造函数私有化

  • 只声明不定义。

    C++11的做法:

class HeapOnly
{
public:
	static HeapOnly* CreateObj()
	{
		//这里new直接去调用构造函数去了,类里面直接调用没限制
		return new HeapOnly;
	}

	//防拷贝,不让拷贝(形参可写可不写)
	HeapOnly(const HeapOnly&) = delete;

	//赋值并不会影响创建的对象跑到其他的地方去了,赋值不是创建对象的
	//拷贝构造反而是用来创建对象的

private:
	//构造函数私有 -- 将构造函数封起来(三条路都封死)
	HeapOnly()
	{}
};

问题:

  1. 那我们在类外调用CreateObj()来创建对象的时候,会有个问题
  2. 那就是著名的先有鸡还是先有蛋的问题
  3. 调用成员函数,就要有对象,要有对象,要有对象,就需要先构造
  4. 我们要解决这个问题就必须打破这个循环死穴 我们将CreateObj()函数设成静态函数

补充:

  • 普通的非静态的构造函数都需要对象去调用
  • 而构造函数不需要

此时还有个问题就是,拷贝构造也是会在栈上创建对象,我们可以将拷贝构造设成私有,或者C++11中直接将拷贝构造给删除掉。

方法二:

相比于上一种方法(将构造函数和拷贝构造私有或者删除),方法二显得更加牵强一点,将析构函数设成私有的,这样当对象生命周期结束时自动调用析构函数时会调用不到。

  • 这种方式的缺陷是:new的时候确实能创建出来,但是在delete释放的时候会报错

解决办法:类外调用不了私有的析构函数,但是类内可以调用,所以我们可以提供一个接口

class HeapOnly
{
public:
	static void DelObj(HeapOnly* ptr)
	{
		delete ptr;
	}

    //比较花的玩法 -- 这样还不用传参了
	/*void DelObj()
	{
		delete this; 
	}*/

private:
	//析构函数私有
	~HeapOnly()
	{}
};

int main()
{
	//HeapOnly h1;
	//static HeapOnly h2;
	HeapOnly* ph3 = new HeapOnly;

	//delete ph3;
	
	//方法一:要传参
	ph3->DelObj(ph3);

	//方法二:不用传参
	//ph3->DelObj();

	return 0;
}

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

类似于上一个问题中的方法,我们先将三条路封死,然后单独给在栈上创建对象开条出路。

思路如下:

  • 先将构造函数私有化
  • 然后在类内写一个函数专门用来创建对象并负责将其传出来
  • 注意:此时传返回值只能是传值返回,不能传指针,也不能传引用
  • 因为是在栈上创建对象,是个局部变量,出了作用域就销毁掉了
class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		//局部对象,不能用指针返回,也不能用引用返回,只能传值返回
		//传值返回必然会有一个拷贝构造发生

		return StackOnly();
	}

	void Print()
	{
		cout << "Stack Only" << endl;
	}
private:
	//构造函数私有
	StackOnly()
	{}
};

int main()
{
	StackOnly h1 = StackOnly::CreateObj();

	//不用对象去接受
	StackOnly::CreateObj().Print();

	//static StackOnly h2;
	
	//禁掉下面的玩法
	//StackOnly* ph3 = new StackOnly;

	return 0;
}

注意:

这里不能将拷贝构造给禁掉,因为CreateObj()是传值返回,传值返回必然会有一个拷贝构造(不考虑编译器优化的问题)。

1.4 请设计一个类,不能被继承:

  • 构造函数私有
  • 只声明不定义
class A
{
private:
	A()
	{}
};

class B : public A
{

};

int main()
{
	B b;
	
	return 0;
}
  • 父类A的构造函数私有化以后,B就无法构造对象
  • 因为规定了子类的成员必须调用父类的构造函数初始化

这时又有一个问题 —— 先有鸡还是先有蛋的问题:

  • 调用成员函数需要对象,对象创建需要调用成员函数,调用成员函数需要对象…
    在这里插入图片描述

如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。
在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

2 单例模式:

概念:

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,一个进程只能有一个对象,并提供一个访问它的全局访问点,该实例被所有程序模块共享.

2.1 饿汉模式:

  • 这种模式是不管你将来用不用这个类的对象,程序启动时就创建一个唯一的实例对象。
  • 创建多线程都是在main函数之后创建的。(main函数之前没有多线程)。
%饿汉模式--一开始(main函数之前)就创建出对象
%优点:简单、没有线程安全问题
%缺点:
 1,一个程序中,多个单例,并且有先后创建初始化顺序要求时,饿汉无法控制
  比如程序中两个单例类A和B,假设要求A先创建初始化,B再创建初始化,
 2.饿汉单例类,初始化时任务多,会影响程序启动速度。


class MemoryPool
{
public:
	static MemoryPool* GetInstance()
	{
		cout << _spInst << endl;
		return _spInst;
	}
	
	void Print();
private:
 //构造函数私有化
	MemoryPool()
	{}

	//防不住拷贝,直接将其删除
	MemoryPool(const MemoryPool&) = delete;
	MemoryPool & operator=(MemoryPool const&) = delete
	
	char*  _ptr=nullptr; //声明
	
	static MemoryPool* _spInst; //声明
};

void MemoryPool::Print()
{
	cout << *_ptr<< endl;
}

MemoryPool* MemoryPool::_spInst = new MemoryPool; //定义

int main()
{
	//GetInstance() 可以或者这个Singleton类的单例对象
	MemoryPool::GetInstance()->Print();

	//在外面就定义不出来对象了
	//MemoryPoolst1;
	//MemoryPool* st2 = new MemoryPool;
	
	//拷贝删除掉
	//MemoryPool copy(*MemoryPool::GetInstance());

	return 0;
}

2.2 懒汉模式:

  • 开始不创建对象,在第一调用GetInstance()的时候再创建对象。

优点:

  • 1.控制顺序
  • 2.不影响启动速度。

缺点:

  • 1.相对复杂(线程安全问题没讲)
  • 2.线程安全问题要处理好
class Singleton
{
public:
   static Singleton* GetInstance() {
    **/ 注意这里一定要使用Double-Check的方式加锁,才能保证效率和线程安全**
    %一定要用双把锁
     if (nullptr == m_pInstance) 
     {
          m_mtx.lock();
         if (nullptr == m_pInstance)
              {
               m_pInstance = new Singleton();
               }
        m_mtx.unlock();
     }
   return m_pInstance;
}

private:
   // 构造函数私有
   Singleton()
    {};
   // 防拷贝
   Singleton(Singleton const&);
   Singleton& operator=(Singleton const&);

   static Singleton* m_pInstance; // 单例对象指针
   static mutex m_mtx; //互斥锁
};

Singleton* Singleton::m_pInstance = nullptr;
mutex Singleton::m_mtx


  • 与饿汉模式不同的是, 懒汉是模式则是一开始不创建对象,而是一开始先给一个指针,这样就避免了程序启动起来很慢的问题。

思路:

  • 将构造函数私有化
  • 一开始不构建对象,只是给一个空指针
  • 在需要的时候再调用其构造函数构造

补充:

  • 懒汉是一种延迟加载,饿汉是一开始就加载

2.3 特殊情况下,单例类的释放

在这里插入图片描述

解决方案:

  • 设计一个内部类
    在这里插入图片描述

尾声
看到这里,相信大家对这个C++有了解了。
如果你感觉这篇博客对你有帮助,不要忘了一键三连哦

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

阿木实验室联合openEuler开源社区-Embedded SlG组(海思项目)参加第五届「开源之夏」,参赛学生火热招募中...

开源之夏是中国科学院软件研究所发起的“开源软件供应链点亮计划”系列暑期活动&#xff0c;旨在鼓励高校学生积极参与开源软件的开发维护&#xff0c;促进优秀开源软件社区的蓬勃发展。活动联合各大开源社区&#xff0c;针对重要开源软件的开发与维护提供项目开发任务&#xf…

bugku 网络安全事件应急响应

开启靶场&#xff1a; 开始实验&#xff1a; 使用Xshell登录服务器&#xff0c;账号及密码如上图。 1、提交攻击者的IP地址 WP: 找到服务器日志路径&#xff0c;通常是在/var/log/&#xff0c;使用cd /var/log/&#xff0c;ls查看此路径下的文件. 找到nginx文件夹。 进入ng…

LabVIEW超高温高压流变仪测试系统

LabVIEW超高温高压流变仪测试系统 超高温高压流变仪广泛应用于石油、天然气、化工等行业&#xff0c;用于测量材料在极端条件下的流变特性。随着计算机技术、测试技术和电子仪器技术的快速发展&#xff0c;传统的流变仪测试方式已无法满足现代工业的需求。因此&#xff0c;开发…

Java——通过方法交换实参值

想写一个方法来交换main函数中的两个变量值&#xff0c;代码如下&#xff1a; public class Test {public static void swap(int x,int y) {int tmp x;x y;y tmp;}public static void main(String[] args) {int a 10;int b 20;System.out.println("交换前&#xff1…

有没有软件可以监控电脑软件?监控电脑软件的系统

有没有软件可以监控电脑软件&#xff1f;监控电脑软件的系统 电脑软件如果不合规也会给企业带来安全危害&#xff0c;比如盗版软件&#xff0c;比如游戏软件耽误工作等&#xff0c;所以需要对电脑软件的监控。下面我将详细介绍几款代表性的电脑监控软件及其功能&#xff0c;帮…

【JAVA基础之内部类】匿名内部类

&#x1f525;作者主页&#xff1a;小林同学的学习笔录 &#x1f525;小林同学的专栏&#xff1a;JAVA之基础专栏 目录 1.内部类 1.1 概述 1.1.1 什么是内部类 1.1.2 什么时候使用内部类 1.2 内部类的分类 1.3 成员内部类 1.3.1 获取成员内部类对象的两种方式 1.3.2 经典面试…

深入pandas:导入数据表

目录 前言 第一点&#xff1a;导入模块 第二点&#xff1a;创建excel表 第三点&#xff1a;读取数据表 总结 前言 数据分析和处理过程中&#xff0c;我们经常需要从外部文件中读取数据。本文将介绍如何使用Python中的Pandas库来读取CSV和Excel文件&#xff0c;以及提取纯数…

v-cloak 用于在 Vue 实例渲染完成之前隐藏绑定的元素

如果你是后端开发者&#xff08;php&#xff09;&#xff0c;在接触一些vue2开发的后台时&#xff0c;会发现有这段代码&#xff1a; # CDN <script src"https://cdn.jsdelivr.net/npm/vue2/dist/vue.js"></script> # 或 <script src"https://cd…

Nacos启动报错:[db-load-error]load jdbc.properties error

在学习Nacos中间件时&#xff0c;出现了一个错误&#xff0c;竟然启动报错&#xff01;&#xff01;&#xff01;! 这个错误第一次遇见&#xff0c;当时我感觉大体就是--数据库连接方面的错误。 可是&#xff0c;对于初学者的我来说一脸懵啊&#xff1f;&#xff1f;&#xff…

微信小程序仿胖东来轮播和背景效果(有效果图)

效果图 .wxml <view class"swiper-index" style"--width--:{{windowWidth}}px;"><image src"{{swiperList[(cardCur bgIndex -1?swiperList.length - 1:cardCur bgIndex > swiperList.length -1?0:cardCur bgIndex)]}}" clas…

代码随想录Day 49|Leetcode|Python|● 647. 回文子串 ● 516.最长回文子序列● 动态规划总结篇

647. 回文子串 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 解题思路&#xff1a; 确认dp含义&#xff1a;dp[i][j] s[i:j]是否为回文串…

k8s 1.24.x之后如果rest 访问apiserver

1.由于 在 1.24 &#xff08;还是 1.20 不清楚了&#xff09;之后&#xff0c;下面这两个apiserver的配置已经被弃用 了&#xff0c;简单的说就是想不安全的访问k8s是不可能了&#xff0c;所以只能走安全的访问方式也就是 https://xx:6443了&#xff0c;所以需要证书。 - --ins…

工况数据导入MATLAB及数据复用

01--数据导入 之前在Matlab/Simulink的一些功能用法笔记&#xff08;二&#xff09;中有介绍过数据的导入到MATLAB工作区间 本次主要是想介绍下数据的复用 我们以NEDC工况数据为例&#xff1a; 通过下列3种方法进行导入&#xff1a; 1.通过导入Excel表数据&#xff0c;使用F…

RH850F1KM-S4-100Pin_ R7F7016453AFP MCAL Gpt 配置

1、Gpt组件包含的子配置项 GptDriverConfigurationGptDemEventParameterRefsGptConfigurationOfOptApiServicesGptChannelConfigSet2、GptDriverConfiguration 2.1、GptAlreadyInitDetCheck 该参数启用/禁用Gpt_Init API中的GPT_E_ALREADY_INITIALIZED Det检查。 true:开启Gpt_…

驱动未来:IT行业的现状与发展趋势

前言 随着技术的不断进步&#xff0c;IT行业已成为推动全球经济和社会发展的关键力量。从云计算、大数据、人工智能到物联网、5G通信和区块链&#xff0c;这些技术正在重塑我们的生活和工作方式。本文将探讨IT行业的现状和未来发展趋势&#xff0c;并邀请行业领袖、技术专家和…

大语言模型量化方法对比:GPTQ、GGUF、AWQ 包括显存和速度

GPTQ: Post-Training Quantization for GPT Models GPTQ是一种4位量化的训练后量化(PTQ)方法&#xff0c;主要关注GPU推理和性能。 该方法背后的思想是&#xff0c;尝试通过最小化该权重的均方误差将所有权重压缩到4位。在推理过程中&#xff0c;它将动态地将其权重去量化为f…

centos7安装jdk的几种方式

一、使用Yum安装 安装OpenJDK的可以选择此方法&#xff0c;方便快捷 1. 查看是否有JDK环境 使用java命令查看 java -version 可以看到系统自带的OpenJDK版本信息&#xff0c;如果满足你现在需要配置的JDK环境&#xff0c;下面的教程可以不用看了。 ps&#xff1a;我这是虚拟机…

前端传参的三种方式

1、params 传参 参数拼接在地址 url 的后面给后台&#xff1b;地址栏中可见 案例1 地址栏&#xff1a;https://xxxxxxxx/admin/clues/detail?id558 接口代码&#xff1a; export function getClueDetail(query: any) {return request<clueItem>({url: /clues/detai…

Web Speech API(1)—— SpeechRecognition

Web Speech API 使你能够将语音数据合并到 Web 应用程序中。Web Speech API 有两个部分&#xff1a;SpeechSynthesis 语音合成&#xff08;文本到语音 TTS&#xff09;和 SpeechRecognition 语音识别&#xff08;异步语音识别&#xff09;。 SpeechRecognition 语音识别通过 S…

C语言指针相关知识(第四篇章)(非常详细版)

文章目录 前言一、什么是回调函数二、qsort函数的介绍(默认升序排序)三、qsort函数的模拟实现&#xff08;通过冒泡排序&#xff09;总结 前言 本文介绍了回调函数&#xff0c;qsort函数的使用&#xff0c;以用冒泡排序来模拟实现qsort函数 提示&#xff1a;以下是本篇文章正文…