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

news2024/11/25 20:45:47

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

方式如下:

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

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

相关文章

了解 3DS MAX 3D摄像机跟踪设置:第 2 部分

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 1. 项目设置 步骤 1 打开“后效”。 打开后效果 步骤 2 转到合成>新合成以创建新合成。 将“宽度”和“高度”值分别设置为 1280 和 720。将帧速率设置为 25&#xff0c;将持续时间设置为 12 秒。单…

该虚拟机似乎正在使用中。如果该虚拟机未在使用,请按”获取所有权(T)”按钮获取它的所有权。否则,请按”取消(C)”按钮以防损坏。

文章目录 一、报错原因二、解决办法 一、报错原因 这是因为虚拟机未被正确关闭导致的问题。因此&#xff0c;在以后使用虚拟机时&#xff0c;我们应该确保正常关闭虚拟机系统&#xff0c;以避免出现这类问题。 二、解决办法 根据提示的配置文件位置&#xff0c;找到虚拟机安…

web-Access注入

看到网站上面有asp的就要联想到access数据库 方法一&#xff1a; 1.判断出整型注入 ?id1513 and 12 2.判断出列数为22列 ?id1513 order by 22 3.猜表名 回显正常说明存在这个表&#xff0c;回显不正常的话说明这个表就是不存在的。这里猜到表名为admin ?id1513 and ex…

详解HTTP协议(一)

1、HTTP概述 1、什么是HTTP 超文本传输协议&#xff08;Hyper Text Transfer Protocol&#xff0c;HTTP&#xff09; HTTP是一个基于请求与响应的、无状态的应用层协议&#xff0c;用于在两点之间传输超文本数据&#xff0c;常基于TCP/IP协议传输数据。 设计HTTP的初衷是为…

vmware17 开启虚拟机虚拟化

背景 我们需要在虚拟机里创建虚拟机。 实体机需要再bios里设置硬件设备的虚拟化 vmware软件可以在软件里设置虚拟化 设置虚拟化 现在我们看到是不能设置的&#xff0c;那是因为我现在开着这个虚拟机系统&#xff0c; 需要先关闭&#xff0c;才能对他进行设置。

【ACM】—蓝桥杯大一暑期集训Day5

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;陈童学哦&#xff0c;目前正在学习C/C、Java、算法等方向&#xff0c;一个正在慢慢前行的普通人。 &#x1f3c0;系列专栏&#xff1a;陈童学的日记 &#x1f4a1;其他专栏&#xff1a;CSTL&#xff…

【Hypermesh】TetraMesh Panel 常用选项详解

我的主页&#xff1a; 技术邻&#xff1a;小铭的ABAQUS学习的技术邻主页博客园 : HF_SO4的主页哔哩哔哩&#xff1a;小铭的ABAQUS学习的个人空间csdn&#xff1a;qgm1702 博客园文章链接&#xff1a; https://www.cnblogs.com/aksoam/p/17575322.html 结合Hypermesh 2020 帮…

Cesium态势标绘专题-辅助文件

本专题没有废话,只有代码,撸! 提示内容封装Tooltip.ts /** 封装提示内容方法* @Author: Wang jianLei* @Date: 2023-01-17 14:56:53* @Last Modified by: jianlei wang* @Last Modified time: 2023-05-19 17:33:37*/ import CreateRemindertip from ./ReminderTip;const T…

Spring-缓存初步认识

Spring-缓存 简单介绍 缓存是一种介于数据永久存储介质和数据应用之间的数据临时存储介质缓存有效提高读取速度&#xff0c;加速查询效率 spring使用缓存方式 添加依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring…

【1++的C++初阶】之list

&#x1f44d;作者主页&#xff1a;进击的1 &#x1f929; 专栏链接&#xff1a;【1的C初阶】 文章目录 一&#xff0c;什么是list二&#xff0c;构造与析构2.1 结点结构2.2 链表结构2.3 迭代器结构 三&#xff0c;部分重要接口的作用及其实现3.1 迭代器相关的接口3.2 list相关…

uniapp 微信小程序 Picker下拉列表数据回显问题

效果图&#xff1a; 1、template <template><view class"items select-box"><view class"items-text">品牌型号</view><picker change"bindBrandType" :value"brandIndex" :range"brandList"…

线性表之链表

1、链表概述 链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 顺序表的存储位置可以用一个简单直观的公式表示&#xff0c;它可以随机存取表中任意一个元素&#xff0c;但插入和删除需要移动大量元素。链式…

【多线程】Synchronize关键字之对象锁和类锁

目录 类锁和对象锁的区别 类锁 对象锁 测试用例 1.m1()和m2()方法都加锁【都为非静态&#xff0c;都加锁互斥执行】 2.m1()加锁&#xff0c;m2()不加锁【都为非静态&#xff0c;互不影响】 3.m3()和m4()都加锁【都为静态&#xff0c;互斥】 4.m3()加锁&#xff0c;m4()不…

WPF快速开发(2):图标库知识点

文章目录 前言知识点windows资源Style:样式Setter:属性继承关系 Trigger:触发器 WPF层级划分数据绑定声明数据上下文绑定数据模板 前言 图标资源下载 iconfont 知识点 windows资源 Window.Resources&#xff1a;资源位置声明X:Key&#xff1a;资源Id&#xff0c;用于前端的…

ARM DAY3 点亮三盏灯

1.汇编代码 .text .global _start _start: //RCC初始化 RCC_INIT://设置GPIOE组使能ldr r0,0x50000A28ldr r1,[r0]orr r1,r1,#(0x1<<4)str r1,[r0]//设置GPIOF组使能 ldr r0,0x50000A28ldr r1,[r0]orr r1,r1,#(0x1<<5)str r1,[r0]//LED1灯初始化 LED1_INIT://设置…

WebLLM项目:在浏览器中运行LLM聊天机器人

大家好&#xff0c;基于LLM的聊天机器人可以通过前端访问&#xff0c;而且它们涉及到大量且昂贵的服务器端API调用。但如果可以让LLM完全在浏览器中运行——利用底层系统的计算能力呢&#xff1f;这样&#xff0c;LLM的全部功能都将在客户端可用——无需担心服务器的可用性、基…

算法竞赛入门【码蹄集新手村600题】(MT1020-1040)

算法竞赛入门【码蹄集新手村600题】(MT1020-1040&#xff09; 目录MT1021 %f格式符MT1022 小数、指数MT1023 进制乱炖MT1024 进制形式MT1025 八、十六进制MT1026 合并MT1027 整数逆序MT1028 四位数逆序MT1029 位数MT1030 最大公约数MT1031 最简分数MT1032 最小公倍数MT1033 多项…

LeetCode[327]区间和的个数

难度&#xff1a;Hard 题目&#xff1a; 给你一个整数数组 nums 以及两个整数 lower 和 upper 。求数组中&#xff0c;值位于范围 [lower, upper] &#xff08;包含 lower 和 upper&#xff09;之内的 区间和的个数 。 区间和 S(i, j) 表示在 nums 中&#xff0c;位置从 i 到 …

一文了解UML

目录 1 什么是UML? 2 UML视图&#xff08;UML View&#xff09; 2.1 用户视图&#xff08;Users View&#xff09; 2.2 结构视图&#xff08;Structural Views&#xff09; 2.3 行为视图&#xff08;Behavioral Views&#xff09; 2.4 环境视图&#xff08;Environmenta…

Vc - Qt - 自定义ComboBox

示例代码创建了一个名为ComboBoxWidget的自定义QWidget类&#xff0c;并在initUI方法中创建了一个垂直布局。然后将一个只读的QLineEdit和一个QPushButton添加到布局中。当按钮被点击时&#xff0c;会调用showMenu方法&#xff0c;该方法创建一个QMenu并添加选项。每个选项连接…