yo!这里是单例模式相关介绍

目录

前言

特殊类设计

只能在堆上创建对象的类

1.方法一(构造函数下手)

 2.方法二(析构函数下手)

只能在栈上创建对象的类

单例模式

饿汉模式实现

懒汉模式实现

后记


前言

        在面向找工作学习c++的过程中,除了基本的语法知识以外,还有一些被反复使用、经验总结的设计模式或者说设计思想值得大家学习,也可以说是面试当中面试官大概率问到的面试题。在本节中,除了介绍标题所述的单例模式,还要了解一些特殊类的设计思想,当然也需要引入这些思想才能更好的理解单例模式。对于常见的特殊类的设计,比如不能被拷贝的类(将拷贝构造函数和赋值运算符重载设置为删除函数)、不能被继承的类(在类名后面加上final关键字)等这些都是一些较为简单的特殊类设计,难一点的像只能在堆或者栈上创建的类、只能创建一个对象的类(单例模式)这些又如何实现?往下看!

特殊类设计

  • 只能在堆上创建对象的类

1.方法一(构造函数下手)

        从构造函数下手,也就是把构造函数私有化,这样我们就可以控制谁能去使用构造函数了,控制的方法就是定义一个共有的接口,在该接口内使用允许的方法创建对象。这里就是创建了一个静态成员函数——getObj(),在接口内去new一个对象返回出去,为什么是静态呢?因为普通成员函数无法在类外使用,必须创建出对象才能使用,这就会产生“先有鸡还是先有蛋”的问题。

        此外,将拷贝构造函数和赋值运算符重载设置为删除函数,是为了防拷贝,因为拷贝与赋值可以在栈上或静态区创建对象,参考代码如下。

代码:

class HeapOnly
{
public:static HeapOnly* getObj(int a){return new HeapOnly(a);}private:HeapOnly(int a):_a(a){}HeapOnly(const HeapOnly& hp) = delete;HeapOnly& operator=(const HeapOnly& hp) = delete;private:int _a = 0;
};

调试:

 2.方法二(析构函数下手)

        除了将构造函数私有化,也可以将析构函数私有化,思想是一样的,即将析构函数私有化,之后定义一个共有的接口去使用允许的方式释放对象,如下代码块的delObj函数,那为什么不需要设置成静态函数呢?因为此时对象已经创建出来了,直接调用此函数来释放资源即可,其他方式创建出来的对象会因为没有析构函数而编译不通过。

        值得提一嘴的是,我们可以不用传入指向资源的指针到delObj函数内,因为普通成员函数有一个参数就是当前对象的指针,直接去释放即可。

代码:

class HeapOnly
{
public:void delObj(HeapOnly* pho){delete pho;}/*void delObj(){delete this;}*/
private:~HeapOnly(){}
private:int _a = 0;
};

调试:

  • 只能在栈上创建对象的类

        只能在栈上创建对象,这种就不能够在析构函数上下手了,考虑一下构造函数,思想其实与只能在堆上创建对象一样——设置共有的静态接口,在接口内定义对象返回出去,但其实这样的做法是不能做到严格把控的,也就是说写不出来只能在栈上创建对象的类,下面解释。

        我们先按之前的思想将其写出来,如下代码块。图一可以看到,的确只能在栈上创建对象,但是看图二,可以通过static将对象拷贝一份在静态区,这就是存在的bug。有人说将拷贝函数和赋值函数都设置为delete函数,但是这样就无法将getObj函数内定义的对象返回出来了,大家可以尝试一下,有解决的方法可以写在评论区,一块讨论。

代码:

class StackOnly
{
public:static StackOnly getObj(int a){return StackOnly(a);}private:StackOnly(int a = 0):_a(a){}public:int _a = 0;
};

单例模式

        一个类只能创建一个对象,即单例模式。单例模式是一种创建对象的设计模式,它确保一个类只有一个实例,并提供全局访问该实例的方式。

        在单例模式中,类的构造函数被私有化,使得外部无法直接创建对象。类内部维护一个静态变量来保存唯一的实例,通过一个公共的静态方法来获取该实例。当第一次调用该方法时,会创建一个对象并赋值给静态变量,以后的调用都直接返回这个静态变量。

        单例模式常用于需要限制全局资源访问的场景,比如数据库连接、线程池等。通过单例模式,可以确保全局只有一个实例存在,并提供一种方便的方式来访问该实例。有两个实现方式来实现单例模式:饿汉模式和懒汉模式

  • 饿汉模式实现

饿汉模式:不管你将来用不用,程序启动时就创建一个唯一的实例对象,也就是一开始(在main函数之前)就创建对象

优点:较简单,不存在线程安全问题

缺点:因为在main函数之前,所以可能会导致进程启动慢,而且如果有多个单例类对象实例启动顺序不确定

实现原理:

        首先,因为是单例模式,只能创建一个对象,所以先将拷贝构造函数、赋值运算符重载函数设置为删除函数,以防止拷贝。其次,按照前面特殊类设计思想,我们依旧将构造函数私有化,并定义一个共有函数用来创建符合条件的对象,这里需要只能创建一个对象,因此我们定义一个静态成员变量(可以是类指针,也可以是类对象),因为静态变量只有一份。静态成员变量是类内声明,类外初始化,因为是一开始就创建,所以在初始化时我们就new一个对象。然后,我们将定义一个共有的静态成员函数来获取这个变量即可,为什么这个成员函数也是静态的?因为普通成员函数内不能使用静态成员变量,代码参考如下:

代码:

class Singleton
{
public:static Singleton* getInstance(){return _inst;}
private:Singleton(){}Singleton(const Singleton& x) = delete;Singleton& operator=(const Singleton& x) = delete;static Singleton* _inst;
};
Singleton* Singleton::_inst = new Singleton();
  • 懒汉模式实现

        当单例对象构造十分耗时或者占用很多资源,比如加载插件、初始化网络连接、读取文件等,而且有可能该对象程序运行时不会用到,那么在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。

懒汉模式:在第一次使用时才创建单例对象

优点:进程启动无负载,多个单例实例启动顺序可以自由控制

缺点:复杂、存在线程安全问题

实现原理:

        如下代码块,懒汉模式实现过程与饿汉模式实现的大致框架都是一样的,主要在于因为懒汉模式是第一次使用时再创建这个对象,因此静态成员变量在初始化时设置为空。先看非线程安全版本的getInstance函数,如果静态成员变量_inst为空,说明还没创建,此时应该去创建这个对象,如果非空,说明已经创建过了,直接返回这个变量出去。

        其次,考虑到多个线程同时竞争创建这个实例,同时判断_inst为空,同时创建出了多个对象,导致线程安全问题,所以我们需要加锁保护,定义并初始化一把静态锁,在判断_inst是否为空之前加锁保护,这样就能保证进入判断的线程只有一个,其他线程都得挂起等待。

        但是发现在加锁的外面又加了一层判空的if语句,这是因为只需要第一次进来时需要用到锁,第二次往后进来都是只需要返回这个指针即可,否则每次进入这个函数都需要申请锁,会降低效率,这种方式叫做Double-Cheack,适用于第一次加锁后续不需要的情形。

        以上是懒汉模式的实现过程,下面再考虑一个问题——单例对象释放问题,首先一般情况下,单例对象是不需要释放的,因为一整个程序都可能需要用到它,单例对象在进程正常结束后也会自动释放;其次,有些场景需要释放,比如单例对象释放时,需要进行持久化操作(将数据信息保存在文件中),那么此时一个释放的方法就是可以通过内部类的方式实现单例对象释放机制。如下面代码块实现的DelInstance类,在成员中定义这样类型的静态对象,当程序结束时,系统会自动调用其析构函数从而释放单例对象,而所谓的持久化操作就可以在其析构函数内做。

代码:

class Singleton
{
public://非线程安全版本/*static Singleton* getInstance(){if (_inst == nullptr){_inst = new Singleton();}return _inst;}*/static Singleton* getInstance(){if(_inst==nullptr){lock_guard<mutex> lg(_mtx);if (_inst == nullptr){_inst = new Singleton();}}return _inst;}class DelInstance{public://持久化操作~DelInstance(){if (_inst)delete _inst;}};static DelInstance _delins;
private:Singleton(){}Singleton(const Singleton& x) = delete;Singleton& operator=(const Singleton& x) = delete;static Singleton* _inst;static mutex _mtx;
};
Singleton* Singleton::_inst = nullptr;
mutex Singleton::_mtx;   //注意:即使不需要初始化,静态成员变量也需要写在这

后记

        通过实现只能在堆或栈上创建对象的类,学习到如何实现单例模式,这是一种设计模式,是大多数程序员共同发现的“套路”,套用相同的模式可以让代码可读性更强、更容易被他人理解、使代码编写更加工程化。实现单例模式也是分为两种方法,其中更加建议使用懒汉模式。不仅仅是学会本文中特殊类设计的方法,更要注重设计的思想,对于面试中面对考官出的其他类的实现题目提供了一种思考或解决办法,好了,以上类的设计自己尝试实现一把,有问题举手。


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

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

相关文章

C语言第十八弹---指针(二)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 指针 1、const修饰指针 1.1、const修饰变量 1.2、const修饰指针变量 2、指针运算 2.1、指针- 整数 2.2、指针-指针 2.3、指针的关系运算 3、野指针 3.1、…

护眼灯几a级的好?最佳的AA级护眼台灯推荐

玩文字游戏&#xff0c;有些商家都是大师级的。我们在各电商平台挑选护眼灯时&#xff0c;都会看到这样一种宣传描述&#xff1a;AAA级全光谱或AAA级健康照明等3个A的字眼。不良品牌厂商在虚假宣传&#xff0c;将国际照明委员会对台灯光线的一个健康认证&#xff0c;也就是AAA级…

C++初阶:适合新手的手撕string类(模拟实现string类)

上次讲了常用的接口&#xff1a;C初阶&#xff1a;初识STL、String类接口详细讲解&#xff08;万字解析&#xff09; 今天就来进行模拟实现啦 文章目录 1.基本结构与文件规划2.构造函数&#xff08;constructor)2.1构造函数2.1.1无参有参分开2.1.2利用缺省参数合起来 2.2拷贝构…

非精线搜索步长规则Armijo规则Goldstein规则Wolfe规则

非精确线搜索步长规则 在数值优化中&#xff0c;线搜索是一种寻找合适步长的策略&#xff0c;以确保在目标函数上获得足够的下降。如最速下降法&#xff0c;拟牛顿法这些常用的优化算法等&#xff0c;其中的线搜索步骤通常使用Armijo规则、Goldstein规则或Wolfe规则等。 设无…

大金焓湿图软件介绍

鼠标在曲线图上选点模式 设置状态点1和状态点2参数模式 旁通分流 下载 https://download.csdn.net/download/jintaihu/16296674

【高质量精品】2024美赛A题数据+多版本前三问代码及代码讲解+前四问思路模型等(后续会更新)

一定要点击文末的卡片&#xff0c;进入后&#xff0c;即可获取完整资料后续参考论文!! 整体分析:这个题目是一个典型的生态系统建模问题&#xff0c;涉及到动物种群的性比例变化、资源可用性、环境因素、生态系统相互作用等多个方面。这个题目的难点在于如何建立一个合理的数学…

mysql按周统计数据简述

概述 业务中经常会遇到按年月日统计的场景&#xff1b; 但有时会有按周统计的情况&#xff1b; 我一般是用2中方法去解决&#xff1a; 利用mysql的weekday函数。计算出当前日期是一周中的第几天&#xff0c;然后当前日期 - 这个数值&#xff0c;就可以得到当前周的周一的日期…

瑞_23种设计模式_抽象工厂模式

文章目录 1 抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;1.1 概念1.2 介绍1.3 小结1.4 结构 2 案例一2.1 案例需求2.2 代码实现 3 案例二3.1 需求3.2 实现 4 总结4.1 抽象工厂模式优缺点4.2 抽象工厂模式使用场景4.3 抽象工厂模式VS工厂方法模式4.4 抽象工厂…

jmeter-03界面介绍

文章目录 主界面介绍工具栏介绍测试计划介绍线程组介绍线程组——选择测试计划&#xff0c;右键-->添加-->线程-->线程组1.线程数2.准备时长(Ramp-up)3.循环次数4.same user on each iteratio5.调度器 主界面介绍 工具栏介绍 新建测试计划&#xff1a;创建一个空白的测…

RNN(神经网络)

目录 介绍&#xff1a; 数据&#xff1a; 模型&#xff1a; 预测&#xff1a; 介绍&#xff1a; RNN&#xff0c;全称为循环神经网络&#xff08;Recurrent Neural Network&#xff09;&#xff0c;是一种深度学习模型&#xff0c;它主要用于处理和分析序列数据。与传统…

HiveSQL题——collect_set()/collect_list()聚合函数

一、collect_set() /collect_list()介绍 collect_set()函数与collect_list()函数属于高级聚合函数&#xff08;行转列&#xff09;&#xff0c;将分组中的某列转换成一个数组返回&#xff0c;常与concat_ws()函数连用实现字段拼接效果。 collect_list&#xff1a;收集并形成lis…

GPT3.5\GPT4系列计算完整prompt token数的官方方法

前言: ChatGPT如何计算token数&#xff1f;https://wtl4it.blog.csdn.net/article/details/135116493?spm1001.2014.3001.5502https://wtl4it.blog.csdn.net/article/details/135116493?spm1001.2014.3001.5502 GPT3.5\GPT4系列计算完整prompt token数的官方方法&#xff1…