【C++】特殊类相关设计

news2025/7/11 5:28:47

前言

        在实际的应用场景中,不免会有一些特殊的设计要求存在。在C++中,由于三种不同的域,以及地址空间的大小或者申请方式不同,就衍生出了一些特殊的设计类方法。

        何为特殊呢?即区别于普通类的设计。

上一篇C++笔记传送门~

【C++】通过哈希表实现map和set_柒海啦的博客-CSDN博客_c++ set 哈希

复习复习C++的类与对象吧~

【C++】类和对象_柒海啦的博客-CSDN博客_c++类和对象

『吉他与孤独与蓝色星球』

目录

1.设计一个类,不可被拷贝

C++98方式:

 C++11方式:

2.设计一个类,只可在堆上创建

限制析构函数:

限制构造函数:

测试代码:

3.设计一个类,只可在栈上创建

4.设计一个类,不可被继承

构造函数私有化:

final关键字: 

5.单例模式

饿汉模式:

懒汉模式:


1.设计一个类,不可被拷贝

        我们知道,在类的默认六个成员函数中(C++11中新增两个,一共八个:构造函数,拷贝构造函数,析构函数,赋值重载函数,取地址重载函数、const取地址重载函数,移动拷贝函数,移动赋值函数),拷贝相关的就是拷贝构造赋值重载相关的函数了。

        我们要设计的类不可被拷贝的话,那我们只要让此类的拷贝构造和赋值重载失效不就好了嘛。

C++98方式:

        (由于此时没有给默认成员函数删除的关键字,所以C++98和C++11是不同的方式)

        因为在类外访问类中的方法都是访问的public区域的。所以,我们将两个拷贝相关的函数放在私有域外界不就可以访问不到了嘛:

class NoCopy
{
public:
	NoCopy(int flag = 0)
		:_flag(flag)
	{}
private:
	int _flag;
	NoCopy(const NoCopy& e)
		:_flag(e._flag)
	{}
	NoCopy& operator= (const NoCopy& e)
	{
		_flag = e._flag;
	}

};

 

 C++11方式:

        由于C++11新增了一个关键字多用,即delete关键字,可以让编译器不再默认生成此默认成员函数,而是删除掉。

        我们删除掉拷贝构造和赋值重载这两个默认成员函数即可。

class NoCopy
{
public:
	NoCopy(int flag = 0)
		:_flag(flag)
	{}
	NoCopy(const NoCopy&) = delete;
	NoCopy& operator= (const NoCopy&) = delete;
private:
	int _flag;
};

 

         另外,有的小伙伴会不会担心移动赋值和移动构造会不会出问题呢?你可以针对上面两种情况利用move函数转为右值试一试,是不会出现问题的。原因就是在C++98的实现方法里面,已经声明实现了拷贝构造、赋值重载函数了,根据移动的默认生成规则,就不会生成。C++11里面delete后也类似于显示写了,也不会。

        移动构造和移动赋值相关默认生成可以详细的看这篇博客哦~

【C++】c++11学习-常用特性总结_柒海啦的博客-CSDN博客

2.设计一个类,只可在堆上创建

        首先,先来区分一下在栈上创建和堆上创建的区别。栈上创建也就是在函数里直接声明定义的;而堆上创建即就是通过malloc或者new等函数或者关键字进行申请的,在C++中需要手动释放的空间。(程序地址空间可以参考这一篇文章:【C++】内存管理到用new申请堆内存_柒海啦的博客-CSDN博客_c++申请堆空间)

        现在,我们想要只能在堆上申请,也就是必须杜绝栈上创建。那么我们就可以先观察类的实例化对象在栈上的表现:创建对象时会调用构造函数在向堆申请的时候new也要调用构造函数),生命周期结束(栈帧结束)会自动调用析构函数堆上需要显示调用delete方可调用析构函数)。

        根据上述表现我们可以找到一个区别:那就是结束的时候栈上的对象是自动调用析构函数,而堆上则是要手动去调用delete才会去调用对象的析构函数的。利用这一个特点,我们就可以用来做手脚。

限制析构函数:

        我们把析构函数放入私有域,那么在栈上创建的对象在生命周期结束后就无法访问析构函数,自然无法在栈上创建。但是堆上虽然可以申请,那么堆上对象如何去释放呢?类中可以提供一个接口,在类里释放即可:

class OnlyHeep1
{
public:
	void Delete()
	{
		delete this;
	}
private:
	~OnlyHeep1()
	{}

	int _a;
};

限制构造函数

        虽然将两者之间的不同点找到可以,但是我们也可以一开始就一棍子打死,限制构造函数(放在私有域),那么这样一来都无法在类外实例化出此类的对象了。所以类里提供一个静态函数(即不需要实例化对象就可以访问的函数),专门返回new出来的对象即可:

        但是需要注意,虽然此接口提供,但是如果不把拷贝构造和赋值重载函数禁用了的话还是可以在栈上创建的,所以还需要对这两个默认成员函数特殊处理

class OnlyHeep2
{
public:
	static OnlyHeep2* New()
	{
		return new OnlyHeep2;
	}

	// 注意细节,拷贝函数相关的就要删掉或者私有化哦
	OnlyHeep2(const OnlyHeep2&) = delete;
	OnlyHeep2& operator=(const OnlyHeep2&) = delete;  
private:
	OnlyHeep2()
		:_a(0)
	{}

	int _a;
};

测试代码:

void test1()
{
	OnlyHeep1* a = new OnlyHeep1;
	a->Delete();

	OnlyHeep2* b = OnlyHeep2::New();
	delete b;
}

3.设计一个类,只可在栈上创建

        和上面那个问题反了过来。由于栈上创建对象没有比堆上多余的创建条件,那么我们也只能限制构造函数,只提供一个返回栈上对象的接口即可。但是注意此时返回的是一个临时对象,所以不可以禁用拷贝构造函数

        这样就会引来一个新的问题,如果不被禁用的话,堆上上创建就可以借此调用拷贝构造函数。所以我们就需要将new操作符进行重载,删除掉即可(也可以放入私有域屏蔽掉即可)

// 构造函数私有化- 但是注意不可禁止拷贝 ,因为返回栈上对象的话由于生命周期问题,所以只能返回拷贝对象。
class OnlyStack
{
public:
	static OnlyStack New()
	{
		return OnlyStack();
	}

	void* operator new(size_t) = delete;
private:
	OnlyStack()
	{}

	int _a = 0;
};

        但是注意上面虽然可以防住堆上创建,但是防不住static创建哦,注意静态变量可不属于栈上的空间哦。所以上面代码是防不住static变量创建的。 

4.设计一个类,不可被继承

构造函数私有化:

        我们知道继承的话,子类是要调用父类的构造函数来对父类域的成员进行初始化的。(详细可以参考这篇文章:【C++】继承- 赋值兼容转换、虚基表_柒海啦的博客-CSDN博客)那么我们将此类的构造函数私有化,这样继承到子类子类对父类的构造函数就是不可见状态,不可见那么也就无法初始化父类域中的成员,就会出现问题。

class NotInheritable2  // C++98
{
public:
private:
	NotInheritable2()
		:_a(0)
	{}
	int _a;
};

        此时发生继承就会存在问题。当然上面的方法存在瑕疵,意思就是我也无法实例化出此类的对象了。

final关键字: 

        C++11中,增加了关键字final在类后表示此类为最终类,即不可被继承。但是可以正常实例化对象:

class NotInheritable1 final  // C++11
{
public:
private:
	int _a;
};

5.单例模式

        存在一种说法叫做设计模式。也就是说在平时写程序中使用较多的一类写法,就会被设计为一种模式:

        软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。

详细请看百度百科:软件设计模式_百度百科 (baidu.com)

        在C++中,迭代器和适配器就是一种设计模式。当然,现在的单例模式就是设计模式中的一种,适用于在一个进程中唯一存在的实例对象。

        针对在一个程序中只能实例化出一个对象的类,我们有如下两种设计模式:饿汉和懒汉模式。

饿汉模式

        顾名思义,饿汉,就是想不顾一切的吃东西。那么它就要率先的去干满足它的事情。

        所以在C++单例模式里的意思就是,在主程序开始前就创建出一个实例对象。由于是在主程序一开始前创造出一个对象,那么一开始就要定义此对象的成员。因为只能实例化一次对象,所以应该将构造函数私有化(既然构造函数私有化,也就会出现上面的拷贝问题,所以也要将这两个禁用掉),提供一个返回此对象的接口,因为是在没有实例化的情况下,返回对象自然就是一个静态成员,方法同样也是静态方法。初始化在外面初始化即可。

// 1饿汉模式 - main开始前就创建对象
class HungryModel
{
public:
	static HungryModel* GetInstance()
	{
		return _pinst;
	}
private:
	HungryModel()  // 构造函数私有化
	{}

	// 防止拷贝
	HungryModel(const HungryModel&) = delete;
	HungryModel& operator= (const HungryModel&) = delete;

	static HungryModel* _pinst;
};
HungryModel* HungryModel::_pinst = new HungryModel;

饿汉模式优缺点:

优点:简单、没有线程安全问题。
缺点:1、一个程序中有多个单例,并且有先后创建初始化顺序要求时,饿汉无法控制。(比如:程序中两个单例类 A 和 B,要求A先创建初始化,B在创建初始化。 - 静态变量是不可确定的(多个文件的情况下)和静动态库)。2、饿汉单例类,初始化时任务多,影响程序启动速度。 

懒汉模式:

        顾名思义,懒汉就是很懒,只有叫到他的时候他才会动一动回应你。

        所以在C++单例模式里的意思就是可在使用接口的时候才进行实例化,但是只能实例化一次。之后就不可进行实例化出对象了。那么实际上也就是从上面的饿汉模式做出一些变化(返回堆上的对象,构造函数进行私有化),在其余不变的条件下,不在外面进行初始化,而是在使用向外提供的接口第一次的时候返回new对象,之后就不可返回对象即可。

        第一次的判断根据成员是否为null进行判断即可:

(下面代码由于博主还没有学习多线程,所以多线程相关安全问题还暂时没有考虑)

// 2懒汉模式 - 第一次使用对象时创建实例
class LazyModel
{
public:
	static LazyModel* GetInstance()
	{
		if (_pinst == nullptr)
		{
			_pinst = new LazyModel;
		}
		return _pinst;
	}
private:
	LazyModel()  // 还是要构造函数私有化
	{}

	// 防止拷贝
	LazyModel(const LazyModel&) = delete;
	LazyModel& operator= (const LazyModel&) = delete;

	static LazyModel* _pinst;
};
LazyModel* LazyModel::_pinst = nullptr;

懒汉模式优缺点:

优点:1.控制顺序。2.不影响启动速度。
缺点:1.相对复杂。2.线程安全问题要处理好。

未完待续~~~

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

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

相关文章

[附源码]计算机毕业设计的高校资源共享平台Springboot程序

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

第二证券|抗原检测板块暴涨,又是20CM涨停!

抗原检测概念起飞,组织以为,抗原检测在国内疫情防控中的重要性有望逐渐提高,主张重视板块潜在的市场需求。 抗原检测板块掀涨停潮 9日,抗原检测概念开盘冲高,到午间收盘,天瑞仪器、英诺特20CM涨停&#xf…

期权量化策略:如何利用期权捕捉期现套利机会?

做期权的朋友请看过来!当前与掘金量化合作的特定券商已经能够支持期权数据和交易接口啦~如需开展期权量化,请联系我了解更多详情。 本期我们将和大家分享一个策略,介绍如何利用期权进行自动化套利。 期现套利是指某种期货合约,当…

浅谈linux 内核网络 sk_buff 之克隆与复制

【推荐阅读】 需要多久才能看完linux内核源码? 概述Linux内核驱动之GPIO子系统API接口 一文了解Linux内核的Oops 一篇长文叙述Linux内核虚拟地址空间的基本概括 纯干货,linux内存管理——内存管理架构(建议收藏) 1 skb_clone() 函…

1X的示波器探头为什么会降低示波器带宽

有些无源示波器探头分为1X和10X两个挡位,比如这个探头,这里有个按钮可以选择1X或者10X, 1X表示测量的信号不在探头衰减,同时示波器的通道选项也不用放大,10X表示测量的信号在探头衰减10倍,同时示波器的通道…

Spring Boot的两种配置文件

⭐️前言⭐️ Spring Boot项目中重要的数据都是在配置文件中配置的,下边我们就来学习SpringBoot中的配置文件的具体详情。 🍉博客主页: 🍁【如风暖阳】🍁 🍉精品Java专栏【JavaEE进阶】、【JavaEE初阶】、…

2.5D游戏,角色移动限制方法。不用空气墙。

有一个项目,2.5D视角。角色在设定好的路线上自由移动,不能超出路线。 之前的做法是用空气墙,设定物理碰撞,然后角色移动。 我感觉这种做法性能有点低。手机上体验平均帧时是4ms 于是想用空间换时间,将可能的运算进行预…

chrome 如何下载网站在线预览PDF文件,保存到本地

爱学习的小伙伴肯定遇到过那种只能在线看,但并不提供下载的的PDF文件! 但有时候想保存到本地有很费劲。今天准备了一个很简单的方法 以这个在线pdf为例 在线PDF文件 该如何把这个PDF保存到本地呢~ 方法 1.以chrome浏览器为例,打开准备好的示…

iOS运行时Runtime在OC中的应用场景

本篇将会总结Rutime的具体应用实例,结合其动态特性,Runtime在开发中的应用大致分为以下几个方面: 一、动态方法交换:Method Swizzling 实现动态方法交换(Method Swizzling )是Runtime中最具盛名的应用场景,其原理是&a…

SpringBoot2.0中MVC和WebFlux控制层Controller对比

本篇文章是SpringBoot2.0关于Controller控制层的对比,相信很多开发最好奇的也是这块。那么小编就带着大家一起先来看一下尝尝鲜,本篇文章比较短小精悍,只讲如何使用,至于原理剖析,后面会讲。阅读时间大概3分钟,现在开始! 文章目录一、演示目录结构二、演示启动类定义…

昨天阅读量900多

今日阅读量还不错的样子,也有900多了,

【C语言】函数递归详解

🚀write in front🚀 📝个人主页:认真写博客的夏目浅石. 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝​ 📣系列专栏:鹏哥带我学c带我飞 💬总结:希望你看…

解析davinci快捷键配置文件

davinci resolve 是一款非线性影片剪辑软件,mac 下快捷键配置文件位于: lucaslucasdeMacBook-Pro DaVinci Resolve % pwd /Users/lucas/Library/Preferences/Blackmagic Design/DaVinci Resolve lucaslucasdeMacBook-Pro DaVinci Resolve % ll | grep k…

“滴灌”代替“漫灌”:“全链路增长”的百度联盟解

作者 | 曾响铃 文 | 响铃说 2021年时,在平台上的日均收益才不足1000元,日活不足1万; 一年时间不到,现在矩阵产品在平台的日均收入已经翻了90倍,日活翻了25倍。 这是一家白牌资讯媒体“早闻天下事”加入百度联盟后发…

跬智信息(Kyligence)荣获浦东新区人工智能创新应用大赛一等奖

近日,2022 浦东新区人工智能创新应用大赛圆满闭幕。经过层层筛选和考核,跬智信息(Kyligence)从 113 支团队中脱颖而出,参赛项目“Byzer 面向 DataAI 的低代码开源编程语言”在技术创新性、创意性以及项目的可落地性、可…

spi访问fpga

SPI 外设的三线/四线模式及时钟极性相位可以配置,支持主机/从机、全双工/半双工,传送数据格式可灵活配置,并且有发送空接收满 SPI 错误等中断事件功能配合应用使用,更多功能详见本系列芯片手册的相关章节。 SPI四线模式框图&…

轻松玩转树莓派Pico之五、FreeRTOS体验

树莓派Pico开发板片上主芯片为RP2040单片机,双核 Arm Cortex-M0 处理器,工作主频为133MHz,264K片上SRAM,和2MByteFlash。 这么大的RAM和Flash资源,不跑一下RTOS操作系统实在有些可惜,这次就先体验一下Fre…

基于新型战争策略优化算法的光伏模型优化(Matlab代码实现)

💥💥💥💞💞💞欢迎来到本博客❤️❤️❤️💥💥💥 🎉作者研究:🏅🏅🏅本科计算机专业,研究生电气学硕…

vscode通过插件一键运行 c++单元测试

gtest使用初探 简介: 本文在 ubuntu18.04 上实现了 googletest 的全局安装,并在一个 demo项目中演示了使用 cmake 引入了该库,实现了在命令行中运行 c 单元测试,包括运行单个 TEST 函数。另外通过 vscode 的插件 C TestMate, 实现…

MATLAB应用2——MATLAB串口采集加速度计数据

串口保存数据为txt格式: clc ; %死机的时候,在命令窗口输入return %getcominstrhwinfo (serial) %寻找串口 delete(instrfindall) %这句话必须有 global xx; global nn; global data1; global s; xx0; nn500; data1(1:nn)0; sserial(COM8); set(s,Bau…