【重点:单例模式】特殊类设计

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

方式如下:

  • 将构造函数设置为私有,防止外部直接调用构造函数在栈上创建对象。
  • 向外部提供一个获取对象的static接口,该接口在堆上创建一个对象并返回。
  • 将拷贝构造函数设置为私有,并且只声明不实现,防止外部调用拷贝构造函数在栈上创建对象。
class HeapOnly
{
public://2、提供一个获取对象的接口,并且该接口必须设置为静态成员函数static HeapOnly* CreateObj(){return new HeapOnly;}
private://1、将构造函数设置为私有HeapOnly(){}//3、将拷贝构造函数设置为私有,并且只声明不实现//C++98HeapOnly(const HeapOnly&);//C++11//HeapOnly(const HeapOnly&) = delete;
};

说明一下:

  • 向外部提供的CreateObj函数必须设置为静态成员函数,因为外部调用该接口就是为了获取对象的,而非静态成员函数必须通过对象才能调用。
  • C++98通过将拷贝构造函数声明为私有以达到防拷贝的目的,C++11可以在拷贝构造函数后面加上=delete,表示让编译器将拷贝构造函数删除,此时也能达到防拷贝的目的。

举例:

class HeapOnly
{
public:static HeapOnly* createObj(){return new HeapOnly;}
private://1、将构造函数设置为私有HeapOnly():_a(0){}//3、将拷贝构造函数设置为私有,并且只声明不实现//C++98HeapOnly(const HeapOnly&);//C++11//HeapOnly(const HeapOnly&) = delete;
private:int _a;};
int main()
{HeapOnly ho1;//栈上//HeapOnly* ptr = HeapOnly::createObj();//HeapOnly copy(*ptr);cout << ptr << endl;return 0;
}

在这里插入图片描述
当我们想通过HeapOnly ho1;在外部直接调用构造函数在栈上创建对象时,我们看到构造函数为私有,防止了我们在栈上创建对象。

因为构造函数私有的关系,我们只能通过提供的获取对象的static接口(createObj()),该接口在堆上创建一个对象并返回,以此来获取堆上的对象。

HeapOnly* ptr = HeapOnly::createObj();

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

方法一:同上将构造函数私有化,然后设计静态方法创建对象返回即可

  • 将构造函数设置为私有,防止外部直接调用构造函数在堆上创建对象。
  • 向外部提供一个获取对象的static接口,该接口在栈上创建一个对象并返回。
class StackOnly
{
public://2、提供一个获取对象的接口,并且该接口必须设置为静态成员函数static StackOnly CreateObj(){return StackOnly();}
private://1、将构造函数设置为私有StackOnly():_a(0){}
private:int _a;
};

但该方法有一个缺陷就是,无法防止外部调用拷贝构造函数创建对象。

StackOnly obj1 = StackOnly::CreateObj();
static StackOnly obj2(obj1); //在静态区拷贝构造对象
StackOnly* ptr = new StackOnly(obj1); //在堆上拷贝构造对象

但是我们不能将构造函数设置为私有,也不能用=delete的方式将拷贝构造函数删除,因为CreateObj函数当中创建的是栈上局部对象,返回局部对象的过程中势必需要调用拷贝构造函数

方法二:屏蔽operator new函数和operator delete函数。

class StackOnly
{
public://2、提供一个获取对象的接口,并且该接口必须设置为静态成员函数static StackOnly CreateObj(){return StackOnly();}private:void* operator new(size_t size) = delete;void operator delete(void* p) = delete;//1、将构造函数设置为私有StackOnly():_a(0){}
private:int _a;
};

new和delete的原理:

  • new在堆上申请空间实际分为两步,第一步是调用operator new函数申请空间,第二步是在申请的空间上执行构造函数,完成对象的初始化工作。
  • delete在释放堆空间也分为两步,第一步是在该空间上执行析构函数,完成对象中资源的清理工作,第二步是调用operator delete函数释放对象的空间。

new和delete默认调用的是全局的operator new函数和operator delete函数,但如果一个类重载了专属的operator new函数和operator delete函数,那么new和delete就会调用这个专属的函数。所以只要把operator new函数和operator delete函数屏蔽掉,那么就无法再使用new在堆上创建对象了。

但该方法也有一个缺陷,就是无法防止外部在静态区创建对象。

static StackOnly obj; //在静态区创建对象

请设计一个类,不能被拷贝

要让一个类不能被拷贝,就要让该类不能调用拷贝构造函数和赋值运算符重载函数,因此直接将该类的拷贝构造函数和赋值运算符重载函数设置为私有,或者用C++11的方式将这两个函数删除即可。

class CopyBan
{
public:CopyBan(){}
private://C++98//CopyBan(const CopyBan&);//CopyBan& operator=(const CopyBan&);//C++11CopyBan(const CopyBan&) = delete;CopyBan& operator=(const CopyBan&) = delete;
};

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

方法一:C++98

将该类的构造函数设置为私有即可,因为子类的构造函数被调用时,必须调用父类的构造函数初始化父类的那一部分成员,但父类的私有成员在子类当中是不可见的,所以在创建子类对象时子类无法调用父类的构造函数对父类的成员进行初始化,因此该类被继承后子类无法创建出对象。

class NonInherit
{
public:static NonInherit CreateObj(){return NonInherit();}
private://将构造函数设置为私有NonInherit(){}
};

方法二:C++11

C++98的这种方式其实不够彻底,因为这个类仍然可以被继承(编译器不会报错),只不过被继承后无法实例化出对象而已。于是C++11中提供了final关键字,被final修饰的类叫做最终类,最终类无法被继承,此时就算继承后没有创建对象也会编译出错。

class NonInherit final
{//...
};

请设计一个类,只能创建一个对象(单例模式)

什么是单例模式?

  • 单例模式是一种设计模式(Design Pattern),设计模式就是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式的目的就是为了可重用代码、让代码更容易被他人理解、保证代码可靠性程序的重用性。
  • 单例模式指的就是一个类只能创建一个对象,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
  • 比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象同一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

单例模式有两种实现方式,分别是饿汉模式和懒汉模式:

饿汉模式

单例模式的饿汉实现方式如下:

  • 将构造函数设置为私有,并将拷贝构造函数和赋值运算符重载函数设置为私有或删除,防止外部创建或拷贝对象。
  • 提供一个指向单例对象的static指针,并在程序入口之前完成单例对象的初始化。
  • 提供一个全局访问点获取单例对象。
class Singleton
{
public://3、提供一个全局访问点获取单例对象static Singleton* GetInstance(){return _inst;}
private://1、将构造函数设置为私有,并防拷贝Singleton(){}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;//2、提供一个指向单例对象的static指针static Singleton* _inst;
};//在程序入口之前完成单例对象的初始化
Singleton* Singleton::_inst = new Singleton;

线程安全相关问题:

  • 饿汉模式在程序运行主函数之前就完成了单例对象的创建,由于main函数之前是不存在多线程的,因此饿汉模式下单例对象的创建过程是线程安全的。
  • 后续所有多线程要访问这个单例对象,都需要通过调用GetInstance函数来获取,这个获取过程是不需要加锁的,因为这是一个读操作。
  • 当然,如果线程通过GetInstance获取到单例对象后,要用这个单例对象进行一些线程不安全的操作,那么这时就需要加锁了。

懒汉模式

单例模式的懒汉实现方式如下:

  • 将构造函数设置为私有,并将拷贝构造函数和赋值运算符重载函数设置为私有或删除,防止外部创建或拷贝对象。
  • 提供一个指向单例对象的static指针,并在程序入口之前先将其初始化为空。
  • 提供一个全局访问点获取单例对象。
class Singleton
{
public://3、提供一个全局访问点获取单例对象static Singleton* GetInstance(){//双检查if (_inst == nullptr){_mtx.lock();if (_inst == nullptr){_inst = new Singleton;}_mtx.unlock();}return _inst;}
private://1、将构造函数设置为私有,并防拷贝Singleton(){}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;//2、提供一个指向单例对象的static指针static Singleton* _inst;static mutex _mtx; //互斥锁
};//在程序入口之前先将static指针初始化为空
Singleton* Singleton::_inst = nullptr;
mutex Singleton::_mtx; //初始化互斥锁

线程安全相关问题:

  • 懒汉模式在程序运行之前没有进行单例对象的创建,而是等到某个线程需要使用这个单例对象时再进行创建,也就是GetInstance函数第一次被调用时创建单例对象。
  • 因此在调用GetInstance函数获取单例对象时,需要先判断这个static指针是否为空,如果为空则说明这个单例对象还没有创建,此时需要先创建这个单例对象然后再将单例对象返回。
  • GetInstance函数第一次调用时需要对static指针进行写入操作,这个过程不是线程安全的,因为多个线程可能同时调用GetInstance函数,如果不对这个过程进行保护,此时这多个线程就会各自创建出一个对象。

双检查加锁:

  • 对GetInstance函数中创建单例对象的过程进行保护,本质就是需要引入互斥锁,最简单的加锁方式就是在进行if判断之前加锁,在整个if语句之后进行解锁。
  • 但实际只有GetInstance函数第一次被调用,创建单例对象时需要使用互斥锁进行保护,而后续调用GetInstance函数获取单例对象只是一个读操作,是不需要使用互斥锁进行保护的。
  • 如果简单的将加锁解锁操作放到if语句前后,那么在后续调用GetInstance函数获取已经创建好的单例对象时,就会进行大量无意义的加锁解锁操作,导致线程不断切入切出,进而影响程序运行效率。
  • 对于这种只有第一次需要加锁保护的场景可以使用双检查加锁,双检查就是在当前加锁和解锁的外面再进行一次if判断,判断static指针是否为空。
  • 这样一来,后续调用GetInstance函数获取已经创建好的单例对象时,外层新加的if判断就会起作用,这样就避免了后续无意义的加锁解锁操作。

饿汉模式和懒汉模式对比

  • 饿汉模式的优点就是简单,但是它的缺点也比较明显。饿汉模式在程序运行主函数之前就会创建单例对象,如果单例类的构造函数中所做的工作比较多,就会导致程序迟迟无法进入主函数,在外部看来就好像是程序卡住了。
  • 此外,如果有多个单例类需要创建单例对象,并且它们之间的初始化存在某种依赖关系,比如单例对象A的创建必须在单例对象B之后,此时饿汉模式也会存在问题,因为我们无法保证这多个单例对象中的哪个对象先创建。
  • 而懒汉模式就能很好的解决上述饿汉模式的缺点,因为懒汉模式并不是一开始就完成单例对象的创建,因此不会导致程序迟迟无法进入主函数,并且懒汉模式中各个单例对象创建的顺序是由各个单例类中的GetInstance函数第一次被调用的顺序决定,因此是可控制的。
  • 懒汉模式的缺点就是,在编码上比饿汉模式复杂,在创建单例对象时需要考虑线程安全的问题。

其他版本的懒汉

懒汉模式还有一种比较经典的实现方式:

  • 将构造函数设置为私有,并将拷贝构造函数和赋值运算符重载函数设置为私有或删除,防止外部创建或拷贝对象。
  • 提供一个全局访问点获取单例对象。
class Singleton
{
public://2、提供一个全局访问点获取单例对象static Singleton* GetInstance(){static Singleton inst;return &inst;}
private://1、将构造函数设置为私有,并防拷贝Singleton(){}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;
};

在单例类的GetInstance函数中定义一个静态的单例对象并返回。

  • 由于实际只有第一次调用GetInstance函数时才会定义这个静态的单例对象,这也就保证了全局只有这一个唯一实例。
  • 并且这里单例对象的定义过程是线程安全的,因为现在的C++标准保证多线程初始化static变量不会发生数据竞争,可以视为原子操作。
  • 该方法属于懒汉模式,因为局部静态变量不是在程序运行主函数之前初始化的,而是在第一次调用GetInstance函数时初始化的。

这种版本的懒汉主要有如下两个缺点:

  • 单例对象定义在静态区,因此太大的单例对象不适合使用这种方式。
  • 单例对象创建在静态区后没办法主动释放。

单例对象的释放

单例对象创建后一般在整个程序运行期间都可能会使用,所以我们可以不考虑单例对象的释放,程序正常结束时会自动将资源归还给操作系统。

如果要考虑单例对象的释放,可以参考以下两种方式:

1.在单例类中编写一个DelInstance函数,在该函数中进行单例对象的释放动作,当不再需要该单例对象时就可以主动调用DelInstance释放单例对象。

static void DelInstance()
{_mtx.lock();if (_inst != nullptr){delete _inst;_inst = nullptr;}_mtx.unlock();
}

2.在单例类中实现一个内嵌的垃圾回收类,在垃圾回收类的析构函数中完成单例对象的释放。在单例类中定义一个静态的垃圾回收类对象,当该对象被消耗时就会调用其析构函数,这时便对单例对象进行了释放。

//垃圾回收类
class CGarbo
{
public:~CGarbo(){if (_inst != nullptr){delete _inst;_inst = nullptr;}}
};

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

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

相关文章

webrtc QOS方法二.4(flexfec 实现可优化点)

一、冗余报文和媒体报文组织结构优化点 以单帧10个媒体报文&#xff0c;冗余度20%为例。这里webrtc输出要有10个媒体包2个冗余包。webrtc输出的报文序列如下&#xff1a; 代码实现如下&#xff1a; UlpfecGenerator::AddPacketAndGenerateFec&#xff1a;攒够足够的帧 Forwar…

【Kafka源码走读】Admin接口的客户端与服务端的连接流程

注&#xff1a;本文对应的kafka的源码的版本是trunk分支。写这篇文章的主要目的是当作自己阅读源码之后的笔记&#xff0c;写的有点凌乱&#xff0c;还望大佬们海涵&#xff0c;多谢&#xff01; 最近在写一个Web版的kafka客户端工具&#xff0c;然后查看Kafka官网&#xff0c;…

Android 屏幕适配各种宽高比的手机

由于android 手机的屏幕宽高比样式太多了&#xff0c;在设计UI时&#xff0c;很多时候&#xff0c;会因为宽高比&#xff0c;分辨率不同会有展示上的差异。 我是这样解决的 在activity的onCreate方法前&#xff0c;调用&#xff1a; fun screenFit(context: Context) {val me…

系统架构设计师-软件架构设计(2)

目录 一、基于架构的软件开发方法&#xff08;ABSD&#xff09; 1、架构需求 1.1 需求获取 1.2 标识构件 1.3 架构需求评审 2、架构设计 2.1 提出架构模型 2.2 映射构件 2.3 分析构件的相互作用 2.4 产生架构 2.5 设计评审 3、架构文档化 4、架构复审 5、架构实现 5.1 分析与…

获取大疆无人机的飞控记录数据并绘制曲线

机型M350RTK&#xff0c;其飞行记录文件为加密的&#xff0c;我的完善代码如下 gitgithub.com:huashu996/DJFlightRecordParsing2TXT.git 一、下载安装官方的DJIFlightRecord git clone gitgithub.com:dji-sdk/FlightRecordParsingLib.git飞行记录文件在打开【我的电脑】&am…

结构型设计模式之装饰器模式【设计模式系列】

系列文章目录 C技能系列 Linux通信架构系列 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 设计模式系列 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everythi…

LiveNVR监控流媒体Onvif/RTSP功能-支持无人机、IPC等设备RTMP推流转码分发H5无插件播放也支持GB28181输出

LiveNVR支持无人机、IPC等设备RTMP推流转码分发H5无插件播放也支持GB28181输出 1、无人机推流转国标2、获取RTMP推流地址2.1、RTMP推流地址格式2.2、推流地址示例 2、设备RTMP推流3、配置拉转RTMP3.1、直播流地址格式3.2、直播流地地址示例3.3、通道配置直播流地址 4、配置级联…

深入浅出多种开发语言对接淘宝京东1688阿里巴巴等电商平台,获取实时商品详情数据API接口介绍

api接口详解大全?优秀的设计是产品变得卓越的原因设计API意味着提供有效的接口&#xff0c;可以帮助API使用者更好地了解、使用和集成&#xff0c;同时帮助人们有效地维护它每个产品都需要使用手册&#xff0c;API也不例外在API领域&#xff0c;可以将设计视为服务器和客户端之…

IDE /完整分析C4819编译错误的本质原因

文章目录 概述基本概念代码页标识符字符集和字符编码方案源字符集和执行字符集 编译器使用的字符集VS字符集配置 有何作用编译器 - 源字符集编译器 -执行字符集 Qt Creator下配置MSVC编译器参数动态库DLL字符集配置不同于可执行程序EXE总结 概述 本文将从根本原因上来分析和解…

属猴人性格及一生运势怎么样?

生肖属猴的人聪明&#xff0c;才华出众&#xff0c;是个非常会处理人际关系的生肖&#xff0c; 开朗&#xff0c;大方&#xff0c;人缘好&#xff0c;而且能说会道&#xff0c;嘴巴甜&#xff0c;也特别擅长社交&#xff0c;喜欢热闹&#xff0c; 所以属猴人不管在哪都容易受到…

数仓学习---13、报表数据导出

星光下的赶路人star的个人主页 莫见长安行乐处&#xff0c;空令岁月易蹉跎 文章目录 一、报表数据导出1.1 MySQL建库建表1.1.1 创建数据库1.1.2 创建表 1.2 数据导出1.2.1 DataX配置文件生成脚本1.2.2 编写每日导出脚本 一、报表数据导出 为方便报表应用使用数据&#xff0c;需…

Java诊断利器 Arthas-- 一款释放潜力的神器

嘿&#xff0c;你是不是对Java开发中的调试和诊断问题感到头疼&#xff1f; 别担心&#xff0c;我要告诉你一个秘密武器&#xff01;它就像是一位超级英雄&#xff0c;能够释放你的潜力&#xff0c;解决你的烦恼&#xff01;它的名字叫做Arthas&#xff0c;是一款Java诊断利器…

手写Nacos基本原理——服务注册 配置管理

手写Nacos基本原理 一、背景介绍二、 思路方案三、过程nacosService代码pom文件配置文件具体类 nacosSDK代码pom文件配置类具体类 serviceA代码pom文件配置文件具体类 serviceB代码pom文件配置文件具体类 实现效果四、总结五、升华 一、背景介绍 之前在项目开发的过程中&#…

C# List 详解五

目录 26.GetType() 27.IndexOf(T) 28.IndexOf(T, Int32) 29.IndexOf(T, Int32, Int32) 30.Insert(Int32, T) 31.InsertRange(Int32, IEnumerable) 32.LastIndexOf(T) 33.LastIndexOf(T, Int32) 34.LastIndexOf(T, Int32, Int32) …

【CAS6.6源码解析】调试Rest API接口

CAS的web层默认是基于webflow实现的&#xff0c;ui和后端是耦合在一起的&#xff0c;做前后端分离调用和调试的时候不太方便。但是好在CAS已经添加了支持Rest API的support模块&#xff0c;添加相应模块即可。 文章目录 添加依赖并重新build效果 添加依赖并重新build 具体添加…

32位Cortex-M4 MCU:LPC54607J256ET180E、LPC54605J512BD100K 180MHz嵌入式微控制器

LPC546xx 32 位微控制器(MCU) 具有丰富的外设集、极低的功耗和增强的调试功能。 LPC546xx MCU系列采用ARM Cortex-M4内核&#xff0c;可提供以太网支持&#xff0c;并设有一个TFT LCD控制器和两个CAN FD模块。LPC546xx MCU旨在提高灵活性和性能可扩展性&#xff0c;可提供高达1…

Vue3 Vite electron 开发桌面程序

Electron是一个跨平台的桌面应用程序开发框架&#xff0c;它允许开发人员使用Web技术&#xff08;如HTML、CSS和JavaScript&#xff09;构建桌面应用程序&#xff0c;这些应用程序可以在Windows、macOS和Linux等操作系统上运行。 Electron的核心是Chromium浏览器内核和Node.js…

ABAP 为N的一个数,在原来基础上浮动在-30~30

需求&#xff1a;为N的一个数&#xff0c;在原来基础上浮动在-30~30 *&---------------------------------------------------------------------* *& Report ZZZZ111 *&---------------------------------------------------------------------* *& 需求&…

【算法基础:搜索与图论】3.4 求最短路算法(Dijkstrabellman-fordspfaFloyd)

文章目录 求最短路算法总览Dijkstra朴素 Dijkstra 算法&#xff08;⭐原理讲解&#xff01;⭐重要&#xff01;&#xff09;&#xff08;用于稠密图&#xff09;例题&#xff1a;849. Dijkstra求最短路 I代码1——使用邻接表代码2——使用邻接矩阵 补充&#xff1a;稠密图和稀疏…

npm i babel-plugin-import -D之后报错

替换modules/.bin/XX文件 1.vue-cli-service #!/bin/sh basedir$(dirname "$(echo "$0" | sed -e s,\\,/,g)")case uname in*CYGWIN*) basedircygpath -w "$basedir";; esacif [ -x "$basedir/node" ]; then"$basedir/node"…