【C++】特殊类设计、单例模式与类型转换

目录

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

(一)C++98

(二)C++11

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

(一)将构造函数私有化,对外提供接口

(二)将析构函数私有化

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

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

(一)C++98

(二)C++11

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

(一)饿汉模式

(二)懒汉模式

六、类型转换

(一)C语言类型转换

(二)C++新增四种强制类型转换

1、static_cast

2、reinterpret_cast

3、const_cast

4、dynamic_cast


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

(一)C++98

class banCopy
{
private:
	banCopy(const banCopy& bc);
	banCopy& operator=(const banCopy& bc);
};

        通过将拷贝构造以及赋值重载私有化使得外部不能调用拷贝构造以及赋值重载。但是对于内部而言仍可以进行类的拷贝。

(二)C++11

class banCopy
{
	banCopy(const banCopy& bc) = delete;
	banCopy& operator=(const banCopy& bc) = delete;
};

        通过使用关键字直接删除相关函数。

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

(一)将构造函数私有化,对外提供接口

class heapOnly
{
public:
	static heapOnly* createObj()
	{
		return new heapOnly;
	}
private:
	heapOnly() {};
	heapOnly(const heapOnly& h) = delete;
	heapOnly& operator = (const heapOnly& h) = delete;
};

        首先将构造函数私有化,可以禁止在栈帧以及静态区创建对象,专门提供创建堆上对象的接口。同时也需要将接口设置为静态成员函数,使得无需对象也可以直接调用该接口。

heapOnly* ph = heapOnly::createObj();

        需要注意的是,也要将拷贝构造与赋值重载删除掉,如果不进行删除,仍然可以通过拷贝对象生成在栈帧或者静态区的对象。

//若不禁用拷贝函数,即可通过拷贝仍在栈帧上生成对象
heapOnly* ph = heapOnly::createObj();
heapOnly h(*ph);

(二)将析构函数私有化

class heapOnly
{
public:
	heapOnly() {  };
	void destoy()
	{
		this->~heapOnly();
	}
private:
	~heapOnly() {  };
};

        利用类与对象的特性:局部对象出作用域会自动调用析构函数释放资源的特性,将析构函数私有化使得在栈上或静态区上开辟的对象会因此导致编译错误,只能在堆上开辟对象。

        因为在堆上开辟的对象需要手动 delete 释放资源,而使用 delete 关键字仍然会调用析构函数(私有化)而导致编译报错,因此可以提供释放资源的接口来实现资源的释放。

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

class stackOnly
{

public:
	static stackOnly createObj()
	{
		return stackOnly();
	}
private:
	stackOnly() {  }
};

        通过构造函数私有化并提供创建对象接口实现只在栈帧上开辟空间。因此使用 new 生成对象会自动调用构造函数,又因构造函数被私有化了因此使用 new 关键字生成对象会编译报错,故禁止了在堆上开辟空间。

        但上述方法并不能禁用从静态区生成对象,仍然可以通过拷贝在静态区生成对象

static stackOnly so = stackOnly::createObj();

        如果想彻底解决以上问题则需要禁用拷贝构造,但是如果禁用了拷贝构造,就不能从 createObj() 返回生成对象了,只能生成临时对象或是临时对象的引用,但是不能修改。

stackOnly::createObj();	//临时对象
const stackOnly& so = stackOnly::createObj();	//临时对象的引用

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

(一)C++98

class finalClass
{
public:
	static finalClass CreateObj()
	{
		return finalClass();
	}
private:
	finalClass() {  }
};

        因为子类构造函数会自动调用父类构造函数完成对父类变量的初始化,通过将父类构造函数私有化,使得子类无法调用父类的构造函数,因此使得该类无法被继承。

(二)C++11

class FinalClass final
{
};

        C++11直接使用 final 关键字使得该类无法被继承。

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

(一)饿汉模式

class InfoSingleton
{
public:
	//返回私有静态成员
	static InfoSingleton& GetInstance()
	{
		return _sins;
	}
	void insert(string name, int salary)
	{
		_um[name] = salary;
	}
	void Print()
	{
		for (auto& e : _um)
		{
			cout << e.first << ":" << e.second << endl;
		}
		cout << endl;
	}
private:
	//构造函数私有化
	InfoSingleton(){}
	//删除拷贝构造以防新建对象
	InfoSingleton(const InfoSingleton& sins) = delete;
	InfoSingleton& operator=(const InfoSingleton& sins) = delete;
	//示例功能 无特殊含义
	unordered_map<string, int> _um;
	//定义静态类型成员
	static InfoSingleton _sins;
};

InfoSingleton InfoSingleton::_sins;	//初始化静态变量

int main()
{
	//下列代码可证明返回的是同一个对象
	InfoSingleton& sins1 = InfoSingleton::GetInstance();
	sins1.insert("张三", 15000);
	sins1.insert("李四", 20000);
	sins1.insert("王五", 10000);
	sins1.insert("赵六", 30000);
	sins1.Print();

	InfoSingleton& sins2 = InfoSingleton::GetInstance();

	sins2.insert("赵六", 20000);
	sins2.Print();
	return 0;
}

        以上是利用所有对象公用一个类静态成员的特性,通过私有构造函数以及删除拷贝函数给出静态类型成员变量的静态接口函数,使得该类只能通过该接口返回静态成员对象

        因为静态成员变量需要在类外进行初始化,而在初始化的同时会自动调用构造函数进行初始化。因此该静态成员对象的初始化,也就是调用构造函数会在main函数之前。

InfoSingleton InfoSingleton::_sins;	//初始化静态变量

        饿汉模式的特点:

        1、 单例对象初始化时,数据太多会导致启动慢;

        2、如果多个单例类有初始化的依赖关系,饿汉模式无法控制。例如A和B都是单例类,因为B的启动依赖A,所以需要先初始化A,再初始化B,但是饿汉模式无法控制对象的初始化顺序;

        3、饿汉模式创建的对象不会有线程安全问题,因为该模式的对象在main函数之前已经被创建好了,而mian函数之前线程都没启动。

(二)懒汉模式

class InfoSingleton
{
	public:
	//返回私有静态成员
	static InfoSingleton& GetInstance()
	{
		if (_sins == nullptr)
		{
			_sins = new InfoSingleton;
		}
		return *_sins;
	}
	void insert(string name, int salary)
	{
		_um[name] = salary;
	}
	void Print()
	{
		for (auto& e : _um)
		{
			cout << e.first << ":" << e.second << endl;
		}
		cout << endl;
	} 
private:
	//构造函数私有化
	InfoSingleton(){}
	//删除拷贝构造以防新建对象
	InfoSingleton(const InfoSingleton& sins) = delete;
	InfoSingleton& operator=(const InfoSingleton& sins) = delete;
	//示例功能 无特殊含义
	unordered_map<string, int> _um;
	//定义静态类型成员
	static InfoSingleton* _sins;
};
InfoSingleton* InfoSingleton::_sins = nullptr; //初始化静态变量

int main()
{
	//下列代码可证明返回的是同一个对象
	InfoSingleton& sins1 = InfoSingleton::GetInstance();
	sins1.insert("张三", 15000);
	sins1.insert("李四", 20000);
	sins1.insert("王五", 10000);
	sins1.insert("赵六", 30000);
	sins1.Print();

	InfoSingleton& sins2 = InfoSingleton::GetInstance();

	sins2.insert("赵六", 20000);
	sins2.Print();
	return 0;
}

        同样的,私有构造函数以及删除拷贝函数并提供生成对象接口都是为了单例模式做准备,与饿汉模式类同。        

        与饿汉模式不同的是,懒汉模式封装的是静态对象指针,也就是在 createObj() 函数第一次进入时会在堆区新建一个对象,而之后的进入则是返回该对象,以此实现只能生成一个对象。

        但是以上的代码存在线程安全问题,多个线程进入 createObj() 函数可能会导致多个线程都会在堆上新建对象导致内存泄漏,因此需要对以上接口进行修改。

class InfoSingleton
{
	public:
	//返回私有静态成员
	static InfoSingleton& GetInstance()
	{
		if (_sins == nullptr)
		{
			_mutex.lock();
			if (_sins == nullptr)
			{
				_sins = new InfoSingleton;
			}
			_mutex.unlock();
		}
		return *_sins;
	} 
private:
	static mutex _mutex;
};

        将 _sins 的检查以及赋值进行加锁保护,这里进行了两层 if 进行条件判断。

        如果只有内部一层 if 判断,会导致每个线程进入该函数都会先加锁,进行判断操作后再解决,降低了系统性能。

        在外层再加一个 if 判断,当第一个线程进入时正常加锁判断操作,但是当之后的线程进入以后,再遇到第一个 if 判断后因为此时 _sins 不为空而无需进行加锁判断了,直接返回 _sins 即可,提高了性能效率。

        懒汉模式的特点:

        1、对象在main函数之后才会创建

        2、可以主动控制对象的创建时机。

        3、创建对象时存在线程安全问题,因此需要用户进行保护控制。

六、类型转换

(一)C语言类型转换

        在C语言中,如果赋值运算符两边类型不同,或是函数形参实参类型不同,或是函数返回值和接收值类型不同时等情况都会发生类型转换。C语言中共有两种形式的类型转换:显式类型转换或是隐式类型转换。

int main()
{
	int a = 0;
	//隐式类型转换
	double b = a;
	int* pa = &a;
	//显示类型转换
	int c = (int)pa;
	return 0;
}

        C语言的类型转换可能会存在一些问题,例如精度丢失或是隐式转换可能带来一些隐藏的bug等,因此C++提出了新的类型转换方式。

(二)C++新增四种强制类型转换

1、static_cast

        static_cast 常用于非多态类型的转换,在C语言中能隐式转换的都可以使用 static_cast 进行转换,其主要用于相近类型转换

int main()
{
	double a = 1.1;
	int b = static_cast<int>(a);
	std::cout << b << std::endl;
	return 0;
}

2、reinterpret_cast

        reinterpret_cast 用于将一种类型转换为另一种不同的类型,常用于不相关类型的转换

int main()
{
	int a = 1;
	int* pa = &a;
	//这里如果使用 static_cast 会编译报错
	int b = reinterpret_cast<int>(pa);
	std::cout << b << std::endl;
	return 0;
}

3、const_cast

        const_cast 用于对具有 const 属性的变量进行转换,常用于删除变量的const属性,方便赋值

int main()
{
	//volatile const int a = 1;
	const int a = 1;
	int* b  = const_cast<int*>(&a);
	++(*b);
	std::cout << a << std::endl << *b << std::endl;
	return 0;
}

        以上代码运行结果为 1 2,使用const_cast 取消了变量 a 的常量属性,但指针 b 仍然指向变量 a 在内存中的位置,因此可以通过指针 b 修改内存中 a 的值。

        对于最后的运行结果在不同平台下的运行结果可能不同,因为编译器对 const 变量会有优化,认为 const 变量在运行中不会改变,因此将 const 变量存入寄存器或告诉缓存中,方便访问读取。而通过指针 b 修改的值实际是修改了于内存中的值,因此打印结果不同。

        可以使用 volatile 关键字保持内存可见性,编译器将取消以上优化仍从内存读取 const 变量,因此加入关键字后打印输出的值相同。

4、dynamic_cast

        以上三种类型转换实际在C语言中已经存在,只是将其更加规范化。

        dynamic_cast 用于将一个父类的指针或引用转换为子类对象的指针或引用。

        向上转型:子类对象指针或引用 -> 父类对象指针或引用 (无需进行转换,发生切片)

        向下转型:父类对象指针或引用 -> 子类对象指针或引用 (使用 dynamic_cast 进行类型转换)

        需要注意的是:dynamic_cast 只能用于父类含有虚函数的类,如果转换失败会返回0。

class A
{
public:
	virtual void func() { }
};
class B : public A
{
};
void function(A* pa)
{
	B* pb1 = static_cast<B*>(pa);
	B* pb2 = dynamic_cast<B*>(pa);
	std::cout << "pb1:" << pb1 << std::endl;
	std::cout << "pb2:" << pb2 << std::endl;
}
int main()
{
	A a;
	B b;
	function(&a);
	function(&b);
	return 0;
}

        以下是上述代码的运行结果,我们来简要分析以下:

        实际上 function() 函数是一个多态调用。如果传入的是类型为 B 的对象,无论是 static_cast 还是 dynamic_cast 转换都可以成功,因为函数参数指针 pa 本身指向的就是类型为 B 的对象,因此可以正常使用转换后的指针。

        但是当传入的是类型为 A 的对象, 我们可以从图可知, static_cast 转换成功了但是 dynamic_cast 转换失败指向为0,而指向为0这也符合 dynamic_cast 的特性。虽然 static_cast 转换成功了,但是在使用该转换后的指针存在着极大的风险会造成未定义行为。

        在以上场景中,相较于 static_cast, dynamic_cast 相当于多了一层检查,如果不能确保能够安全转换,那不建议使用 static_cast 进行强制类型转换。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微服务网关鉴权之sa-token

目录 前言 项目描述 使用技术 项目结构 要点 实现 前期准备 依赖准备 统一依赖版本 模块依赖 配置文件准备 登录准备 网关配置token解析拦截器 网关集成sa-token 配置sa-token接口鉴权 配置satoken权限、角色获取 通用模块配置用户拦截器 api模块配置feign…

16.好数python解法——2024年省赛蓝桥杯真题

问题描述 一个整数如果按从低位到高位的顺序,奇数位(个位、百位、万位…)上的数字是奇数,偶数位(十位、千位、十万位…)上的数字是偶数,我们就称之为“好数”。 给定一个正整数N,请计算从1到N一共有多少个好数。 输入格式 一个整数N。 输出格式 一个整数代表答案。 样例输入 1 …

2025年数学建模美赛 A题分析(2)楼梯使用频率数学模型

2025年数学建模美赛 A题分析&#xff08;1&#xff09;Testing Time: The Constant Wear On Stairs 2025年数学建模美赛 A题分析&#xff08;2&#xff09;楼梯磨损分析模型 2025年数学建模美赛 A题分析&#xff08;3&#xff09;楼梯使用方向偏好模型 2025年数学建模美赛 A题分…

Redis实战(黑马点评)——涉及session、redis存储验证码,双拦截器处理请求

项目整体介绍 数据库表介绍 基于session的短信验证码登录与注册 controller层 // 获取验证码PostMapping("code")public Result sendCode(RequestParam("phone") String phone, HttpSession session) {return userService.sendCode(phone, session);}// 获…

Brightness Controller-源码记录

Brightness Controller 亮度控制 一、概述二、ddcutil 与 xrandr1. ddcutil2. xrandr 三、部分代码解析1. icons2. ui3. utilinit.py 一、概述 项目&#xff1a;https://github.com/SunStorm2018/Brightness.git 原理&#xff1a;Brightness Controlle 是我在 Ubuntu 发现上调…

STM32简介

STM32简介 STM32是ST公司基于ARMCortex-M内核开发的32位微控制器 &#xff08;Microcontroller&#xff09; MCU微控制器、MPU微处理器、CPU中央处理器 1.应用领域 STM32常应用于嵌入式领域。 如智能车&#xff1a;循迹小车 读取光电传感器或者摄像头的数据&#xff0c;…

qt-C++笔记之QLine、QRect、QPainterPath、和自定义QGraphicsPathItem、QGraphicsRectItem的区别

qt-C笔记之QLine、QRect、QPainterPath、和自定义QGraphicsPathItem、QGraphicsRectItem的区别 code review! 参考笔记 1.qt-C笔记之重写QGraphicsItem的paint方法(自定义QGraphicsItem) 文章目录 qt-C笔记之QLine、QRect、QPainterPath、和自定义QGraphicsPathItem、QGraphic…

浏览器IndexedDB占用大

使用鲁大师清理后&#xff0c;用 SpaceSniffer 查看C盘占用情况&#xff0c;发现浏览器的 IndexedDB 有3个文件夹占用特别大&#xff0c;从文件名看是 youku&#xff0c;bilibili&#xff0c;v.qq.com&#xff0c;浏览器的数据库并不需要长期保存&#xff0c;删除这3个文件夹&a…

MongoDB部署模式

目录 单节点模式&#xff08;Standalone&#xff09; 副本集模式&#xff08;Replica Set&#xff09; 分片集群模式&#xff08;Sharded Cluster&#xff09; MongoDB有多种部署模式&#xff0c;可以根据业务需求选择适合的架构和部署方式。 单节点模式&#xff08;Standa…

将 OneLake 数据索引到 Elasticsearch - 第二部分

作者&#xff1a;来自 Elastic Gustavo Llermaly 及 Jeffrey Rengifo 本文分为两部分&#xff0c;第二部分介绍如何使用自定义连接器将 OneLake 数据索引并搜索到 Elastic 中。 在本文中&#xff0c;我们将利用第 1 部分中学到的知识来创建 OneLake 自定义 Elasticsearch 连接器…

Formality:时序变换(三)(相位反转)

相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 一、引言 时序变换在Design Compiler的首次综合和增量综合中都可能发生&#xff0c;它们包括&#xff1a;时钟门控(Clock Gating)、寄存器合并(Register Merging)、…

php代码审计2 piwigo CMS in_array()函数漏洞

php代码审计2 piwigo CMS in_array()函数漏洞 一、目的 本次学习目的是了解in_array()函数和对项目piwigo中关于in_array()函数存在漏洞的一个审计并利用漏洞获得管理员帐号。 二、in_array函数学习 in_array() 函数搜索数组中是否存在指定的值。 in_array($search,$array…

房租管理系统的智能化应用助推租赁行业高效运营与决策优化

内容概要 在现代租赁行业中&#xff0c;房租管理系统的智能化应用正在逐步成为一个不可或缺的工具。通过整合最新技术&#xff0c;这些系统为租赁管理的各个方面提供了极大的便利和效率提升。从房源管理到合同签署再到财务监控&#xff0c;智能化功能能够帮助运营者在繁琐的事…

Hive关于数据库的语法,warehouse,metastore

关于数据库的语法 在default数据库下,查看其他数据库的表 in 打开控制台 字体大小的设置 Hive默认的库: default, 1/4说明一共有4个库,现在只展示了1个,单击>>所有架构 数据库的删除 方法一: 语法 删除有表的数据库,加cascade 方法二 当前连接的数据库 切换当前数据库…

【React】PureComponent 和 Component 的区别

前言 在 React 中&#xff0c;PureComponent 和 Component 都是用于创建组件的基类&#xff0c;但它们有一个主要的区别&#xff1a;PureComponent 会给类组件默认加一个shouldComponentUpdate周期函数。在此周期函数中&#xff0c;它对props 和 state (新老的属性/状态)会做一…

AI赋能医疗:智慧医疗系统源码与互联网医院APP的核心技术剖析

本篇文章&#xff0c;笔者将深入剖析智慧医疗系统的源码架构以及互联网医院APP背后的核心技术&#xff0c;探讨其在医疗行业中的应用价值。 一、智慧医疗系统的核心架构 智慧医疗系统是一个高度集成的信息化平台&#xff0c;主要涵盖数据采集、智能分析、决策支持、远程医疗等…

HTML-新浪新闻-实现标题-样式1

用css进行样式控制 css引入方式&#xff1a; --行内样式&#xff1a;写在标签的style属性中&#xff08;不推荐&#xff09; --内嵌样式&#xff1a;写在style标签中&#xff08;可以写在页面任何位置&#xff0c;但通常约定写在head标签中&#xff09; --外联样式&#xf…

【学习笔记】深度学习网络-深度前馈网络(MLP)

作者选择了由 Ian Goodfellow、Yoshua Bengio 和 Aaron Courville 三位大佬撰写的《Deep Learning》(人工智能领域的经典教程&#xff0c;深度学习领域研究生必读教材),开始深度学习领域学习&#xff0c;深入全面的理解深度学习的理论知识。 在之前的文章中介绍了深度学习中用…

如何在IDEA社区版Service面板中管理springboot项目

1、开启service仪表盘 2、在service仪表盘中&#xff0c;添加启动类配置项&#xff0c;专业版是SpringBoot 、社区版是application。 3、控制台彩色日志输出 右键启动类配置项&#xff0c;添加虚拟机参数 -Dspring.output.ansi.enabledALWAYS

如何在data.table中处理缺失值

&#x1f4ca;&#x1f4bb;【R语言进阶】轻松搞定缺失值&#xff0c;让数据清洗更高效&#xff01; &#x1f44b; 大家好呀&#xff01;今天我要和大家分享一个超实用的R语言技巧——如何在data.table中处理缺失值&#xff0c;并且提供了一个自定义函数calculate_missing_va…