【C++】特殊类

news2024/11/27 2:33:30


目录

前言

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

C++98方法

C++11方法

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

方法一

方法二

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

方法一

方法二

四、设计一个类,不能被继承

C++98方式

C++11方法

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

设计模式

单例模式:

饿汉模式

懒汉模式


前言

面试中,考官有时候会问一些特殊类的设计,今天我们来介绍一下常见的特殊类的设计方式。


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

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此 想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可
  

C++98方法

将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
//无法拷贝的类
class CopyBan
{
public:
	CopyBan()
	{}

private:
	CopyBan(const CopyBan&);

	CopyBan& operator=(const CopyBan&);
};
 
int main()
{
	CopyBan cb1;
	CopyBan cb2(cb1);

	return 0;
}

原因:   

1. 设置成私有:如果只声明没有设置成 private ,用户自己如果在类外定义了,就不能禁止拷贝了。
2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

     

C++11方法

C++11 扩展 delete 的用法, delete 除了释放 new 申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。
//无法拷贝的类
class CopyBan
{
public:
	CopyBan()
	{}

private:
	CopyBan(const CopyBan&) = delete;

	CopyBan& operator=(const CopyBan&) = delete;
};

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

方法一

实现方式:
1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。 (静态作用在于不需要对象也能直接调用该函数)
//只能在堆区构造的类
class HeapOnly
{
public:
	static HeapOnly* Create()
	{
		return new HeapOnly;
	}

private:
	HeapOnly(){}
	HeapOnly(const HeapOnly&) = delete;
};

int main()
{
    HeapOnly* hp = HeapOnly::Create();
}

需要的使用的时候,我们用指针来接收返回的对象。

关于delete释放问题:

因为是我们用new开辟出来的,一般我们是要delete来释放空间的,但是一般情况下用完进程就会回收资源了。只要不是一直跑的程序开辟了空间不回收就行。

   

方法二

实现方法:

1.将析构函数私有化,因为如果不能调用析构,那么就无法创建对象,编译器会报错。

2.提供一个成员函数,类内调用析构函数销毁对象。

class HeapOnly
{
public:
	HeapOnly()
	{}

	void Destroy()
	{
		this->~HeapOnly();
	}
		 
private:
	~HeapOnly(){}

};

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

方法一

禁用opeartor new 和operator delete。
 

我们通常用new和delete来创建销毁对象都是其在类内会调用opeartor new 和operator delete,我们如果将其在类内禁用了,那么就无法使用new和delete来创建对象了。

class StackOnly
{
public:
	void* operator new(size_t size) = delete;
	void operator delete(void* p) = delete;
};

方法二

将构造函数私有,然后提供一个静态函数Create,在函数中调用构造函数。

class StackOnly
{
public:
	static StackOnly Creat()
	{
		return StackOnly();
	}
private:
	StackOnly(){}
};

分析:

因为我们需要用到拷贝构造,所以不能封掉拷贝构造。 但是我们却可以通过以下的方式创建静态变量。

所以这里设计的只能在栈区创建的对象是有缺陷的。


四、设计一个类,不能被继承

C++98方式

构造函数私有化,派生类中调不到基类的构造函数来完成初始化,则无法继承。
class StackOnly
{
public:
	static StackOnly Create()
	{
		return StackOnly();
	}
private:
	StackOnly(){}

	int _a;
};

class NonInherit : public StackOnly
{
public:
	NonInherit()
	{}
private:
	int _b;
};

   

C++11方法

fifinal关键字,fifinal修饰类,表示该类不能被继承。
class NonInherit final
{
	//......
};

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

设计模式

设计模式( Design Pattern )是一套 被反复使用、多数人知晓的、经过分类的、代码设计经验的 总结
设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

   

单例模式:

一个类只能创建一个对象,即单例模式 ,该模式可以保证系统中该类只有一个实例,并提供一个 访问它的全局访问点,该实例被所有程序模块共享 。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

   

单例模式分为两种:

  • 饿汉模式

不管你将来用不用,程序启动时就创建一个唯一的实例对象。
class Singleton
{
public:
	static Singleton& GetInstance()
	{
		return m_instance;
	}

	void fun()
	{
		cout << "Singleton::func()" << endl;
	}
private:
	Singleton() {}

	Singleton(const Singleton&) = delete;

	Singleton& operator= (const Singleton&) = delete;

private:
	//静态变量
	static Singleton m_instance;

};
Singleton Singleton::m_instance;  //类外初始化

1.我们在Singleton类中添加一个Singleton类的静态变量,并且在在类外初始化,这样整个类就这一个静态的对象。

2.需要获取的时候我们利用静态函数GetInstance返回,获取到类内的静态对象。

3.我们需要对类内成员进行操作的时候,只需要在类内创建对应的函数即可。

 

接收我们可以用引用接收:

 优缺点:

优点:简单
缺点:占用更多的资源,可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
  
  • 懒汉模式

如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
class Singleton
{
public:
	static Singleton& GetInstance()
	{
		if (m_instance == nullptr)
		{
			m_instance = new Singleton;
		}

		return *m_instance;
	}

private:
	Singleton(){}

	Singleton(const Singleton&) {}
	Singleton& operator= (const Singleton&) {}

	static Singleton* m_instance;
};
Singleton* Singleton::m_instance = nullptr;

懒汉模式的线程安全问题

在多线程模式下,我们执行if判断的时候,如果刚好进了if语句但是此时时间片时间到了,线程被切走,那么另外一个线程开辟了空间分配给了m_instance,结束后切回最初的进程重复进行开辟空间分配的操作,那么此时就会造成内存泄漏的问题。

为了解决这个问题,我们需要对存在线程安全的代码进行加锁。

class Singleton
{
public:
	static Singleton& GetInstance()
	{
		_mtx.lock();
		if (m_instance == nullptr)
		{
			m_instance = new Singleton;
		}
		_mtx.unlock();

		return *m_instance;
	}

private:
	Singleton(){}

	Singleton(const Singleton&) {}
	Singleton& operator= (const Singleton&) {}

	static Singleton* m_instance;
	static mutex _mtx;
};
Singleton* Singleton::m_instance = nullptr;
mutex Singleton::_mtx;

因为只有第一次进if语句的时候才用得上锁,其他的时候也加锁是会浪费性能的。对此我们可以加上一个双检查判断来优化一下。

static Singleton& GetInstance()
{
	if (m_instance == nullptr)
	{
		_mtx.lock();
		if (m_instance == nullptr)
		{
			m_instance = new Singleton;
		}
		_mtx.unlock();
	}

	return *m_instance;
}

我们上面的代码中其实还是有一个问题:点那个new失败抛异常之后,锁的unlock操作就不会执行了。为了解决这个问题,我们可以用try-catch的方法来解决,但我们这里还可以用智能指针的办法来自动释放。

利用RAII机制自动回收锁

我们可以创建一个智能锁指针,构造函数中加锁,析构函数中解锁。从而达到了锁的自动加锁和解锁。

class MutexGard
{
public:
	MutexGard(mutex& mtx)
		:_mtx(mtx)
	{
		_mtx.lock();
	}

	~MutexGard()
	{
		_mtx.unlock();
	}
private:
	mutex& _mtx; //所不允许拷贝,这里只能用引用
};

class Singleton
{
public:
	static Singleton& GetInstance()
	{
		if (m_instance == nullptr)
		{
			/*_mtx.lock();
			if (m_instance == nullptr)
			{
				m_instance = new Singleton;
			}
			_mtx.unlock();*/

			MutexGard mg(_mtx);
			if (m_instance == nullptr)
			{
				m_instance = new Singleton;
			}
		}
		
		return *m_instance;
	}

private:
	Singleton(){}

	Singleton(const Singleton&) {}
	Singleton& operator= (const Singleton&) {}

	static Singleton* m_instance;
	static mutex _mtx;
};
Singleton* Singleton::m_instance = nullptr;
mutex Singleton::_mtx;

资源的回收

一般情况下,资源是不需要我们自动回收的,但有时候,可能要将数据写进文件中进行保存。我们就需要手动在函数内保存了。

void DelInstance()
{
	//保存数据的操作
	// ......

	if (m_instance != nullptr)
	{
		delete m_instance;
		m_instance = nullptr;
	}
}

为了省事,我们也可以封装成一个自动保存资源的类,然后在单例类中加入了一个资源回收类的对象,这样在最后调用析构的时候,就能够自动回收资源了。

class GC
	{
	public:
		~GC()
		{
			//保存数据的操作
			// ......

			if (m_instance != nullptr)
			{
				delete m_instance;
				m_instance = nullptr;
			}
		}
	};

 懒汉模式的简易方法

class Singleton {
public:
	static Singleton& GetInstance()
	{
		static Singleton m_instance;
		return m_instance;
	}

	Singleton(const Singleton& sin) = delete;
	Singleton& operator=(const Singleton& sin) = delete;

private:
	Singleton() {}
};

这种方法实现的非常简单巧妙:

1.因为是静态的变量,就算反复调用该函数,都只会在静态区开辟一个变量,并不会反复开辟。

2.并不需要在堆区开辟空间创建单例对象。

3.不需要考虑线程安全问题并加锁以及new抛异常问题

上述方法虽然巧妙,但是值得一提的是,只有在C++11之后的版本中才能保证局部创建的静态变量是线程安全的。

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

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

相关文章

darknet yolo标注、训练详细说明

文章目录 1、标注数据1.1、标注1.2、生成训练列表文件train.txt1.3、转换数据标注格式 2、训练数据整理2.1、修改train.txt路径2.2、修改yolov3.cfg2.3、obj.name和obj.data2.4、训练脚本文件trian.sh2.5、测试脚本文件test.sh 3、训练 本文对应的脚本文件和程序下载链接 darke…

chatgpt赋能python:Python如何保存成文件

Python如何保存成文件 Python是一种十分强大和流行的编程语言&#xff0c;它的灵活性和易于使用使得它成为了一个应用广泛的语言。在Python中&#xff0c;我们可以使用多种方式将编写的代码保存成文件&#xff0c;从而能够实现对需要的内容的复用和分享。本文将会介绍Python中…

利用谷歌DevTool解决web网页内存泄漏问题

目录 web网页内存泄漏 主要的内存泄漏来源 利用谷歌DevTool定位内存泄漏问题 性能Performance 主要功能 Performance insights性能数据分析 Memory内存 三种模式 相关概念 解决内存泄漏问题 第一步 &#xff1a;是否内存泄漏&#xff1a;js堆直增不降&#xff1b;降…

001Mybatis常用的网站及工具

MyBatis中文网https://mybatis.net.cn/ MyBatis Dynamic SQL – MyBatis Dynamic SQLhttps://mybatis.org/mybatis-dynamic-sql MyBatis GitHubMyBatis has 37 repositories available. Follow their code on GitHub.https://github.com/mybatis/ mybatis-spring-boot-auto…

探究Jetpack(二)之LiveData

目录 LiveData的基本用法比较安全的LiveData使用方法map和switchMapmapswitchMap LiveData是Jetpack提供的一种响应式编程组件&#xff0c;它可以包含任何类型的数据&#xff0c;并在数据发生变化的时候通知给观察者 LiveData特别适合与ViewModel结合在一起使用&#xff0c;虽然…

大数据Doris(三十七):Spark Load导入HDFS数据

文章目录 Spark Load导入HDFS数据 一、准备HDFS数据 二、创建Doris表 三、创建Spark Load导入任务

C语言编程—常量

常量是固定值&#xff0c;在程序执行期间不会改变。这些固定的值&#xff0c;又叫做字面量。 常量可以是任何的基本数据类型&#xff0c;比如整数常量、浮点常量、字符常量&#xff0c;或字符串字面值&#xff0c;也有枚举常量。 常量就像是常规的变量&#xff0c;只不过常量…

云原生系列一:Aeraki --- 管理 Istio 服务网格中任何 7 层协议

导语:Aeraki Mesh 是 CNCF 的沙箱项目,它可以帮助你在服务网格中管理任何七层协议。 今天由叶秋学长来介绍如何通过 Aeraki 来在服务网格中为 Dubbo、Thrift 等协议的服务提供七层流量路由、本地限流、全局限流,以及如何基于 Aeraki Protocol快速开发一个自定义协议,并在 I…

CKA 06_Kubernetes 工作负载与调度 Pod 管理 yaml 资源清单 标签 Pod 生命周期 容器探针

工作负载与调度 1. Pod 管理1.1 kubectl 命令 2. yaml 资源清单2.1 yaml 文件的格式2.2 编写 yaml 资源清单 3. 标签3.1 节点标签选择器 考试题目&#xff1a;pod 中运行 nginx 和 memcache 容器4. Pod 生命周期4.1 Init 容器 5. 容器探针5.1 探测类型5.2 配置存活、就绪和启动…

驱动进化之路:总线设备驱动模型

了解总线设备驱动模型之前&#xff0c;可以先了解常规驱动程序的编写&#xff1a; LED驱动程序框架 驱动设计的思想&#xff1a;面向对象/分层/分离&#xff08;以LED操作为例&#xff09; 此次总线设备驱动模型程序的编写基于上述两种框架。 1. 总线设备驱动模型框架 在led_d…

STM32开发——串口通讯(非中断+中断)

目录 1.串口简介 2.非中断接收发送字符 3.中断接收字符 1.串口简介 通过中断的方法接受串口工具发送的字符串&#xff0c;并将其发送回串口工具。 串口发送/接收函数&#xff1a; HAL_UART_Transmit(); 串口发送数据&#xff0c;使用超时管理机制HAL_UART_Receive(); 串口…

虚拟机中Ubuntu不知root密码时设置唯一的非root用户为sudo管理员

刚才在虚拟机中玩Ubuntu的时候&#xff0c;在Cockpit管理页面把账号“服务器管理员”的选项给取消了&#xff0c;然后重新登录之后&#xff0c;就不能执行sudo指令了&#xff0c;所有需要root权限才能访问的文件&#xff08;目录&#xff09;、执行的命令全部都不行了。 执行s…

数据传输中的数据转换与处理的常用方法-物联网开发-单片机通信

目录 一、前言 二、实践与代码 1.Unsigned Char 2.memset 3.sprintf 4.atoi 5.atof 6.strcmp 7.strtok 8.strlen 9.strcpy 10.strcat 三、总结 一、前言 本文将以STM32单片机为基础&#xff0c;使用Keil5环境展示以下方法。 在单片机通信、载波通信中&#xff0c;常常涉及数…

【Laravel 6】安装需要什么环境?又怎么安装呢

服务器要求 服务器满足以下要求&#xff1a; PHP > 7.2.5BCMath PHP 拓展Ctype PHP 拓展JSON PHP 拓展Mbstring PHP 拓展OpenSSL PHP 拓展PDO PHP 拓展Tokenizer PHP 拓展XML PHP 拓展 安装Laravel Laravel 使用 Composer 来管理项目依赖。因此&#xff0c;在使用 Larav…

JDK动态代理和cglib代理

文章目录 前言1.JDK动态代理1.1 定义一个接口1.2 实现接口1.3 自定义MyInvocationHandler去实现InvocationHandler接口1.4 测试jdk代理1.5 输出代理类 2.cglib代理2.1 代理接口类2.2 代理普通类2.3 设置属性生成cglib代理类 前言 动态代理在平时的开发中用的也很多&#xff0c…

算法与数据结构(五)

一、c二叉树结构 typedef struct BiNode {int data; //结点内值域struct BiNode* lchild, * rchild; //左孩子 右孩子 } BiNode, * BiTree;或者&#xff1a; class Node { public:char data;// data表示该节点代表的内容&#xff1b;Node* L;//左子树Node* R;//右子树 }; No…

深度学习之卷积神经网络识别图片验证码实战案例(十)

案例背景&#xff1a;程序自动化的爬虫而无需人工介入是我们的最终目标。自动化爬虫避免不了自动登录的问题&#xff0c;在爬取XX数据的过程中&#xff0c;遇到登录图形验证码的识别的问题&#xff0c;那我们该如何攻破这种验证码呢&#xff1f; 字符验证码图片如下&#xff1a…

facenet, dlib人脸识别,人体检测,云数据库mysql,QQ邮箱,手机验证码,语音播报

目录 部分代码展示&#xff1a; 录入部分 识别部分​编辑 活体检测部分​编辑 同步到云数据库MySQL 其他操作 部分图片展示&#xff1a; 完整代码加ui链接&#xff1a; 涉及到的一些知识点的文章 部分代码展示&#xff1a; 录入部分 识别部分 活体检测部分 同步到云数…

峰终定律原理

峰终定律 峰终定律&#xff08; Peak–End Rule&#xff09;&#xff0c;是由丹尼尔卡尼曼&#xff08;2002年诺贝尔经济学奖获得者&#xff0c;心理学家&#xff09;提出的。 模型介绍 峰终定律是指如果在一段体验的高峰和结尾&#xff0c;体验是愉悦的&#xff0c;那么对整个…

走进机器学习

作者简介&#xff1a;本人是一名大二学生&#xff0c;就读于人工智能专业&#xff0c;学习过c&#xff0c;c&#xff0c;java&#xff0c;python&#xff0c;Mysql等编程知识&#xff0c;现在致力于学习人工智能方面的知识&#xff0c;感谢CSDN让我们相遇&#xff0c;我也会致力…