C++ 特殊类的设计

news2025/1/14 18:23:45

文章目录

    • 1. 设计一个只能在堆上创建对象的类
    • 2. 设计一个只能在栈上创建对象的类
    • 3. 设计一个类不能被拷贝
    • 4. 设计一个类 不能被继承
    • 5. 设计一个类,只能创建一个对象

前言: 在本文中,我们掌握几种常见的特殊类的设计。


1. 设计一个只能在堆上创建对象的类

大部分情况下,我们创建的对象都是在栈上,你只要定义一个对象 那么它就在栈上。 要求对象只能在堆上创建,所以 要对 类的构造函数 做些调整。

思路:

  • 将构造函数设置为私有,那么 普通的创建对象方式 就给封死了。
  • 提供一个静态成员函数,此函数返回一个 在堆上创建的对象 。
  • 拷贝构造 它得销毁掉。

代码实现:

class Heap_class
{
private:
	int _a;
	Heap_class(int a = 0)
		:_a(a)
	{}
public:
	static Heap_class* Creat_object(int n)
	{
		return new Heap_class(n);
	}
	Heap_class(const Heap_class&) = delete;
};

使用:

Heap_class* hc= Heap_class::Creat_object(2);

这就是简单的使用,可以看到这个类中有一个私有成员 _a ,所以设计 出构造这个对象,返回的是一个匿名对象,用传的参数n来初始化_a。


如果 不把拷贝构造销毁掉,会出现这样的情况:

Heap_class* hc= Heap_class::Creat_object(2);
	 
Heap_class hh(*hc);

hh就是在栈上开辟的,所以 还是把拷贝构造封掉。


还有就是 有人可能对 设置一个静态成员函数有问题,为啥要设置为静态呢?

静态成员函数 没有 this指针,它不受对象的管理。简言之,没有对象 它依旧可以调用;如果是普通的成员函数,有this指针,调用普通成员函数,必须先有对象,然而对象 还没创建。这就类似先有鸡后有蛋的问题。


2. 设计一个只能在栈上创建对象的类

上面是只能在堆上,现在要求只能在栈上。和上面的思路如出一辙。

还是得控制 构造函数,如何控制?或者 不需要控制 构造函数,而是去控制 new 、delete

  1. 设置静态成员函数,返回一个在栈上创建的对象
  2. 屏蔽new

代码实现:

  1. 第一个版本:
class stack_class
{
private:
	int _a;
	stack_class(int a=0)
		:_a(a)
	{}
public:
	static stack_class&& Creat_object(int n)
	{
		return stack_class(n);
	}
};

使用:

 stack_class s= stack_class::Creat_object(3);
  1. 第二个版本:
class stack_class1
{
private:
	int _a;
	void* operator new(size_t n) = delete;
	void operator delete(void* ptr) = delete;

public:
	stack_class1(int a=0)
		:_a(a)
	{}
};

使用: 正常使用就行、

 stack_class1 s1(2);

3. 设计一个类不能被拷贝

这就更简单了啊,直接将拷贝构造和赋值重载 delete掉:

class A
{
private:
	int _a;
public:
	A(int a = 0)
		:_a(a)
	{}
	A(const A&) = delete;
	A& operator=(A&) = delete;
};


4. 设计一个类 不能被继承

这个给出两种方法:

  1. 控制构造函数,将基类的构造函数设置为私有,就会导致继承此基类的子类,无非初始化基类的东西,从而导致构造子类对象失败。这就可以理解为 基类不能被构造,这是C++98的玩法。
  2. 利用关键字 final 修饰类,这是c++11 才有的。

代码实现:

class B
{
private:
	B(){}
};

class s: public B
{
public:
	s(){}
};

这里其实就已经编译不通过了,因为 基类的构造函数无法访问。

在这里插入图片描述
这里如果不懂为什么,建议去看看我之前的博客,有一篇是专门讲继承的。


class B final
{
public:
	B(){}
};

class s: public B
{
public:
	s(){}
};

在这里插入图片描述


5. 设计一个类,只能创建一个对象

其实这里就涉及到设计模式了,一个类只能创建一个对象,那就是单例模式。单例模式,又有两种实现方式:懒汉,饿汉。

思路:

慢慢来讲这个;首先我提个问题:全局变量 在多个源程序中如何使用?问的有点别扭,我们来看代码:

头文件 声明 还有 一个配套的源程序 定义 ;再来一个源程序有来测验:

hc.h:

int a;

hc.cpp:

#include"hc.h"
int a = 1;

test.cpp:

#include<iostream>
#include"hc.h"
using namespace std;

int main()
{
 cout<<a<<endl;
}

像上面那样写,行嘛?看结果:

在这里插入图片描述
怎么办?说明一个问题:头文件处对 a变量也是定义,而不是声明,声明一个 变量前 要加 extern

hc.h:

extern int a;

但是怎么说呢,上面对于一个全局变量 让其他程序共用,用的这种办法是C语言的玩法;到了C++ 我们可以利用面向对象的思想,去设计。

首先,全局变量 让所有程序共用;对应到C++里就是,所有程序,共用一个对象。那么这个类必须 有且只能创建一个对象,也就是单例模式。


  1. 饿汉模式

hc.h:

class A
{
private:
 	int _a;
    static A my_A;
	A(int a = 0):_a(a)
	{}
public:
	void add_a()
	{
		_a++;
	}

	void del_a()
	{
		_a--;
	}
	A(const A&) = delete;
	A& operator=(const A) = delete;

public:
	static A& use_myA()
	{
		return my_A;
	}
};
A A::my_A; /// 程序入口,就已经创建好了对象

使用:

#include"hc.h"
int main()
{
	A::use_myA().add_a();
	A::use_myA().add_a();
	A::use_myA().add_a();
	A::use_myA().del_a();
	A::use_myA().del_a();
}

通过调试看看结果:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

分析一下饿汉模式:类中有一个静态对象,在程序入口时,已经初始化完成;构造函数依旧给成私有,表明 有且只有一个静态对象供使用;给了两类函数接口,第一个类是static A& use_myA():它是返回了静态对象 ,第二类是void add_a(),void del_a() 操作对象中的成员。


  1. 懒汉模式

懒汉模式,它不会一上来就创建对象,而是 当用到这个对象时,才会去创建对象,供使用。

class B
{
private:
	int _b;
	B(int b=0)
		:_b(b)
	{}

	static B* my_B;
public:
	static B& use_myB()
	{
		if (my_B == nullptr)
		{
			my_B = new B;
		}
		return *my_B;
	}
	void add_b()
	{
		_b++;
	}

	void del_b()
	{
		_b--;
	}
	B(const B&) = delete;
	B& operator=(const B) = delete;

};

B* B::my_B = nullptr; // 先给成空指针

懒汉就是 给的不是对象 而是一个对象指针,它为空时,对象不会构建;当使用它时,判断其为 空,那么构造一个对象。

使用也很简单:

    B::use_myB().add_b();
	B::use_myB().add_b();
	B::use_myB().del_b();

但是懒汉模式 还需要 进一步完善,那就是需要考虑线程安全问题,所以需要 使用互斥锁 。
出现线程安全问题的地方:

       if (my_B == nullptr)
		{
			my_B = new B;
		}

如果多线程,进来,就有可能导致 构造出多个对象。比如:一个线程判断为空,被打断,另一个线程 进来构建了一个对象,切回到上一个线程,然而 第一个线程已经进入 if语句,所以 它又构造了一个对象。

所以需要在这里加上锁。

还有一点要改善的地方在于,在最后 要回收 静态对象,因为它是在堆上创建的,所以需要内嵌一个回收类。

完整版本:

class B
{
private:
	int _b;
	B(int b=0)
		:_b(b)
	{}

	static B* my_B;
	static mutex _mutex;
public:
	static B& use_myB()
	{
		if (my_B == nullptr)
		{
			unique_lock<mutex>(_mutex);
			if(my_B == nullptr)
			my_B = new B;
		}
		return *my_B;
	}
	void add_b()
	{
		_b++;
	}

	void del_b()
	{
		_b--;
	}
	B(const B&) = delete;
	B& operator=(const B) = delete;

	class reclaim
	{
	public:
		~reclaim()
		{
			if (my_B)
			{
				delete my_B;
				my_B = nullptr;
			}
		}
	};

	static reclaim rm_myb;

};

B* B::my_B = nullptr;
mutex B::_mutex;
B::reclaim rm_myb;

可以看到这就是完整版本的了,有内嵌类型,并且还有一个细节就是 上锁那块。

一般情况下,我们上锁是这样的:

   static B& use_myB()
	{
	    unique_lock<mutex>(_mutex);
		if (my_B == nullptr)
		{
			my_B = new B;
		}
		return *my_B;
	}

但是这样上锁,会导致 线程进来都去竞争锁资源。其实只有在 my_B 为空的时候,才需要竞争锁资源去创建一个对象。当对象创建成功,就不需要再竞争锁资源了。因为单例模式嘛,共用一个对象的。

可以优化的 这里:

    static B& use_myB()
	{
		if (my_B == nullptr)
		{
			unique_lock<mutex>(_mutex);
			if(my_B == nullptr)
			my_B = new B;
		}
		return *my_B;
	}

但是 必须是 双判断 ,如果 单判断 其实 就相当于没加这个锁:

比如:

    static B& use_myB()
	{
		if (my_B == nullptr)
		{
			unique_lock<mutex>(_mutex);
			my_B = new B;
		}
		return *my_B;
	}

上面这种,就是 单判断 相当于没加锁;给出一种场景,第一个线程判断为空 并竞争上锁资源,创建了一个对象;但在没创建对象成功前,另一个进程 也判断为空成功,并进入了if语句,但是竞争锁资源失败了。第一个线程 创建对象成功后,释放锁资源,第二线程紧接着获取锁资源,然后又创建了一个对象。 嗯 ,问题就出现了。

综上 ,这里需要用到双判断。

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

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

相关文章

微信小程序使用vant 和 mobx 自动定义Tabbar

vant 和 mobx 自动定义Tabbar 在此案例中&#xff0c;用到的主要知识点如下&#xff1a; 自定义组件 Vant 组件库 MobX 数据共享 组件样式隔离 组件数据监听器 组件的 behaviors Vant 样式覆盖 1.首先需要给我们的app.json 配置tabBar “custom”&#xff1a;true 注意点&…

代码随想录算法训练营第五十九天| LeetCode503. 下一个更大元素 II、LeetCode42. 接雨水

一、LeetCode503. 下一个更大元素 II 1&#xff1a;题目描述&#xff08;503. 下一个更大元素 II&#xff09; 给定一个循环数组 nums &#xff08; nums[nums.length - 1] 的下一个元素是 nums[0] &#xff09;&#xff0c;返回 nums 中每个元素的 下一个更大元素 。 数字 x 的…

实践 DevOps 测试策略

什么是 DevOps 测试策略&#xff1f; DevOps 的一个重要组成部分是持续集成/持续交付(CI/CD)&#xff0c;在 CI 和 CD 之间的就是持续测试。 如果不进行持续测试&#xff0c;将会出现&#xff1a; 缺陷的泄漏软件延期交付客户不满意DevOps 测试策略的好处 可以提供更快的反…

如何在vscode、remix中结合hardhat编译部署合约

创建 hardhat 工程 # 创建npm空项目&#xff0c;注意这里要选择合约项目对应的文件目录 npm init # 安装 hardhat 环境&#xff0c;这里安装的版本 2.11.1 npm install --save-dev hardhat2.11.1 # 创建工程 npx hardhat首先创建 npm 空项目&#xff0c;注意这里要选择合约项目…

Linux编程环境

一、实验目的 1&#xff0e;熟悉Linux下C语言程序设计的基本步骤 2&#xff0e;掌握gcc编译器的各种参数的使用方法 3&#xff0e;掌握gcc编译器创建函数库的方法 4&#xff0e;掌握gdb调试程序的方法 5&#xff0e;掌握多文件编译中的makefile的用法 二、实验软硬件要求…

matlab学习笔记(八)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 matlab学习笔记&#xff08;八&#xff09;一、傅里叶变换的MATLAB求解二、连续时间信号的频谱图三、MATLAB分析LTI系统的频率特性一、傅里叶变换的MATLAB求解 MATLAB的symb…

大学生网页设计制作作业实例代码 (全网最全,建议收藏) HTML+CSS+JS

文章目录&#x1f4da;web前端期末大作业 (1500套) 集合一、网页介绍二、网页集合三、作品演示A电影主题B漫画主题C商城主题D家乡主题E旅游主题F餐饮/美食主题G环境主题H游戏主题I 个人主题K体育主题L博客主题M汽车主题N文化主题P美妆主题Q企业主题R教育主题S其他主题&#x1f…

Docker的私有仓库部署——Harbor

一.Docker原生私有仓库—— Registry 1.1 Registry的简单了解 关于Docker的仓库分为私有库和公有仓库&#xff0c;共有仓库只要在官方注册用户&#xff0c;登录即可使用。但对于仓库的使用&#xff0c;企业还是会有自己的专属镜像&#xff0c;所以私有库的搭建也是很有必要的…

力扣(LeetCode)1780. 判断一个数字是否可以表示成三的幂的和(C++)

进制转换 转换 333 进制&#xff0c;如果每一位非 000 即 111 &#xff0c;returntruereturn\ truereturn true (数字 000 除外&#xff09;。 如果任意一位为 222 &#xff0c;returnfalsereturn\ falsereturn false 。 证明: 对于第 ppp 位&#xff0c; 如果 p0p0p0 &#…

数据结构——树和二叉树最全总结(期末复习必备)

目录 树和二叉树 树的基本术语&#xff08;均以上图b为例&#xff09;&#xff1a; 遍历二叉树&#xff1a; 线索二叉树&#xff1a; 树的存储结构&#xff1a; 树与二叉树的转换&#xff08;利用的就是把二叉树和树表示成相同的二叉链表&#xff09;&#xff1a; 森林与二…

KubeSphere 接入外部 Elasticsearch 最佳实践

作者&#xff1a;张坚&#xff0c;科大讯飞开发工程师&#xff0c;云原生爱好者。 大家好&#xff0c;我是张坚。今天来聊聊如何在 KubeSphere 中集成外置的 ES 组件。 KubeSphere 在安装完成时候可以启用日志组件&#xff0c;这样会安装 ES 组件并可以收集所有部署组件的日志…

索引优化学习

背景 最近做查询优化&#xff0c;学到的。字段长度&#xff0c;索引长度联合索引计算是否使用范围查询使用索引 字段长度&#xff08;varchar&#xff09; 只谈论varchar&#xff1a;首先我们建表varchar(20) 中的20是字符数。看你的数据库编码 执行&#xff1a;show creat…

支持多种网关类型!米尔基于Zynq-7010/20开发平台工业网关设计应用

随着工业物联网的飞速的发展&#xff0c;5G时代的到来&#xff0c;工业控制系统在生产领域应用越来越广泛&#xff0c;工业物联网为未来工业控制系统灵活性和可扩展性的需求提供了支持。工业物联网使我们的生产数据可以进行规模化集中存储&#xff0c;并利用高速采集、云计算等…

ChatGPT国产平替出现了:APP商店就能下载,还可给AI加人设,背后公司刚成立3个月...

明敏 发自 凹非寺量子位 | 公众号 QbitAIChatGPT太火爆谁不想上手试试&#xff1f;但注册复杂、服务器拥挤……着实有点麻烦。不过很快就有极客网友指路&#xff0c;说国内其实已经有类似的APP上线了&#xff0c;也是上知天文下知地理的那种。比如聊聊《三体》&#xff0c;还会…

Transformer 训练优化

前言 自 BERT 出现以来&#xff0c;NLP 领域已经进入了大模型的时代&#xff0c;大模型虽然效果好&#xff0c;但是毕竟不是人人都有着丰富的 GPU 资源&#xff0c;在训练时往往就捉襟见肘&#xff0c;出现显存 out of memory 的问题&#xff0c;或者训练时间非常非常的久&…

web期末大作业:基于html+css+js制作深圳大学网站(13页) 学校班级网页制作模板 学生静态HTML网页源码

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

【easypoi 模板导出嵌套 list 问题】

easypoi 模板导出多 list 问题1背景1.1 espoi 模板定义1.2 导出结果1.3发现共享单车的数据没有显示&#xff0c;手动操作取消共享单车的单元格合并1.4手动取消单元格合并后数据&#xff0c;正常再把框线画好1.5 代码操作&#xff1a;用 esaypoi 处理到1.2&#xff0c;用基础 po…

聊聊与前端工程师天然互补的 Serverless

作为前端工程师&#xff0c;我们的使命是为用户提供良好的前端用户体验。随着云原生时代的到来&#xff0c;显而易见的&#xff0c;我们能做的更多了。Serverless 产品的特点是免运维、按量付费和自适应弹性&#xff0c;所以我们可以利用云上的各种 Serverless 能力&#xff0c…

《程序员的自我修养》程序实现的两大环境

学习内容 翻译环境 预处理 编译 汇编 链接 执行环境 在标准C的任何一种实现中&#xff0c;都存在两个不同的环境&#xff1a; 1.翻译环境&#xff1a;在这个环境中&#xff0c;源代码被翻译成为可执行的机器指令。 2.执行环境&#xff1a;用于执行实际的代码 在VS2022中&…

字符串的读入方式

文章目录1、scanf2、fgets()3、cin4、cin.getline()5、getline()1、scanf scanf只能读入不带空格的字符串&#xff0c;遇到空格则结束。scanf只能读入字符数组&#xff0c;不能读入string。scanf在读入的时候&#xff0c;会自动在字符串的末尾加上’\0’。定义字符数组长度时&…