C++—特殊类设计设计模式

news2025/5/19 14:15:36

目录

  • C++—特殊类设计&设计模式
    • 1.设计模式
    • 2.特殊类设计
      • 2.1设计一个无法被拷贝的类
      • 2.2设计一个只能在堆上创建对象的类
      • 2.3设计一个只能在栈上创建对象的类
      • 2.4设计一个类,无法被继承
      • 2.5设计一个类。这个类只能创建一个对象【单例模式】
        • 2.5.1懒汉模式实现
        • 2.5.2饿汉模式实现
        • 2.5.3懒汉的饿汉的区别

C++—特殊类设计&设计模式

1.设计模式

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结

为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似

之前已经接触过一些设计模式了

比如:

  1. 迭代器模式
  2. 适配器模式

还有一些设计模式——工厂模式,装饰器模式,观察者模式,单例模式、

2.特殊类设计

下面是一些常见的特殊类设计

2.1设计一个无法被拷贝的类

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

这个前面在异常的时候其实就说过了,两种方式,分别是c++98和c++11提供的

  • c++11的方法:

C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。

class NotCopy
{
	// 其实就是将拷贝构造和赋值运算符重载给禁掉就行了,之前有讲过
	// 在c++98就是将之声明不定义,然后将两个成员函数弄成私有的,这样外面就无法使用者两个函数了
	// 在c++11可以使用delete关键字
public:
	NotCopy(const NotCopy& n) = delete;
	NotCopy& operator=(const NotCopy& n) = delete;
private:
	int _a;
};
  • c++98的方法:
class NotCopy
{
	// 在c++98就是将之声明不定义,然后将两个成员函数弄成私有的,这样外面就无法使用者两个函数了
	// 在c++11可以使用delete关键字
public:

private:
    NotCopy(const NotCopy& n);
	NotCopy& operator=(const NotCopy& n);
    
	int _a;
};
  • 原因:
  1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不

能禁止拷贝了

  1. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

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

实现方式:

  1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。

  2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建

//2.设计一个只能在堆上创建对象的类
class OnlyHead
{
public:
	static OnlyHead* GetObj()
	{
		return new OnlyHead;
		// new OnlyHead和new OnlyHead()的区别
		// 就是第一个是隐式调用默认构造函数
		// 第二个是显式调用默认构造函数
	}

	// 除了构造函数不能给外面直接调用,拷贝构造和赋值运算符重载也要禁掉
	OnlyHead(const OnlyHead& o) = delete;
	OnlyHead& operator=(const OnlyHead& o) = delete;

private:
	// 在栈上定义的对象一定会调用默认构造函数,所以直接弄成私有的
	OnlyHead()
	{}

};


int main()
{
	//OnlyHead hp;
	//OnlyHead* p = new OnlyHead;
	OnlyHead* p = OnlyHead::GetObj(); 
	// 如果不释放p就会内存泄漏
	shared_ptr<OnlyHead> sp(OnlyHead::GetObj()); //交给智能指针
	//OnlyHead copy(*sp); // 拷贝构造要禁掉

	return 0;
}

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

可以用2.2的思路,先将构造私有,然后直接给一个类内静态方法

也可以将new直接重载,直接禁掉

// 3.设计一个只能在栈上创建对象的类
class OnlyStack
{
public:
	// 第一种思路: 直接不给new,重载new【这个思路无法阻止静态区的对象的创建】
	void* operator new(size_t size) = delete;

	// 第二种思路:将构造函数设置为私有的,然后提供一个static方法给外部调用
	// 这个思路不能禁掉拷贝构造,因为getobj返回值这里会有一次拷贝构造
	//【这个思路无法阻止静态区的对象的创建】
	static OnlyStack getobj()
	{
		return OnlyStack(); //类里面可以调用私有的构造函数
	}
private:
	OnlyStack()
	{}
};

int main()
{
	OnlyStack* p = new OnlyStack;
	OnlyStack* p = new OnlyStack();
	
	OnlyStack o = OnlyStack::getobj(); //创建栈上的对象,注意这个思路不能禁掉拷贝构造
	// 这里实际上调用了拷贝构造

	static OnlyStack os = OnlyStack::getobj();


	return 0;
}

2.4设计一个类,无法被继承

这个简单,就不仔细说了

// 4.设计一个类,无法被继承 
class NoInherit final
{
	// 两个思路,c++98:直接让构造函数私有化,因为子类的构造需要调用父类的构造
	// c++11:final关键字
};

2.5设计一个类。这个类只能创建一个对象【单例模式】

2.5.1懒汉模式实现

单例模式:一个类在全局(进程)中只能有一个实例对象

// 5.设计一个类。这个类只能创建一个对象【单例模式】
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		if (_p == nullptr)
		{
			_p = new Singleton;
		}

		return _p;
	}

	// 在这个情况下,拷贝构造也要禁掉
	Singleton(const Singleton& s) = delete;

private:
	Singleton()
	{}

	// 需要再创建一个变量,来控制对象只能存在一个
	// 其实就是弄一个所有对象共用的变量,只要该变量标记了,就说明已经有一个对象了,那就不能再调用构造函数了
	static Singleton* _p; 
};

Singleton* Singleton::_p = nullptr; //初始化

int main()
{
	Singleton* s1 = Singleton::GetInstance();
	Singleton* s2 = Singleton::GetInstance();
	Singleton* s3 = Singleton::GetInstance();
	// 这里三个获取的都是同一个对象
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	
	//Singleton s4(Singleton::GetInstance());//拷贝构造要禁掉

	return 0;
}

image-20250506085655120

但是上面这个代码是有问题的,这个单例模式会有线程安全问题,因为当多线程操作的时候,这里有一个公共资源,即临界资源——_p,因此就会出现线程安全问题。可能会创建出多个对象。这样单例模式就被破坏了

因此对临界资源_p加锁即可

#include<mutex>
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		_mtx.lock();
		if (_p == nullptr)
		{
			_p = new Singleton;
		}
		_mtx.unlock();

		return _p;
	}

	// 在这个情况下,拷贝构造也要禁掉
	Singleton(const Singleton& s) = delete;

private:
	Singleton()
	{}

	// 需要再创建一个变量,来控制对象只能存在一个
	// 其实就是弄一个所有对象共用的变量,只要该变量标记了,就说明已经有一个对象了,那就不能再调用构造函数了
	static Singleton* _p; 
	// _p是一个临界资源,要保护它
	static mutex _mtx; //为了线程安全,加一个锁
};

Singleton* Singleton::_p = nullptr; //初始化
mutex Singleton::_mtx; //定义, 类里面的只是声明

这个代码就不会出现线程安全问题。

但是这个代码还是有点问题,因为虽然对临界区加锁了,但是临界区内有可能发生异常,new可能会抛异常,一旦抛异常就会造成死锁,因为这里应该用RAII思想来解决

#include<mutex>
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		//_mtx.lock();
		{ //这个花括号是为了控制unique_lock的生命周期,不影响临界区后面的代码
			unique_lock<mutex> lock(_mtx);
			if (_p == nullptr)
			{
				_p = new Singleton;
			}
		}
		//_mtx.unlock();

		return _p;
	}

	// 在这个情况下,拷贝构造也要禁掉
	Singleton(const Singleton& s) = delete;

private:
	Singleton()
	{}

	// 需要再创建一个变量,来控制对象只能存在一个
	// 其实就是弄一个所有对象共用的变量,只要该变量标记了,就说明已经有一个对象了,那就不能再调用构造函数了
	static Singleton* _p; 
	// _p是一个临界资源,要保护它
	static mutex _mtx; //为了线程安全,加一个锁
};

Singleton* Singleton::_p = nullptr; //初始化
mutex Singleton::_mtx; //定义, 类里面的只是声明

上述代码仍然有一些缺陷,可以继续优化。【即,我们只需要对第一次访问临界资源_p进行加锁保护即可,因为后面不会再有修改临界资源的情况出现】

这里做一个经典的双检查

static Singleton* GetInstance()
{
	//_mtx.lock();
	if(_p == nullptr) //这里是一个双检查
	{ 
		unique_lock<mutex> lock(_mtx);
		if (_p == nullptr)
		{
			_p = new Singleton;
		}
	} //并且出了这里,unique_lock<mutex>的生命周期就结束了
	//_mtx.unlock();

	return _p;
}

下面是完整的代码:【这里是一个懒汉模式的单例模式】

#include<mutex>
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		//_mtx.lock();
		if(_p == nullptr) //这里是一个双检查
		{ 
			unique_lock<mutex> lock(_mtx);
			if (_p == nullptr)
			{
				_p = new Singleton;
			}
		} //并且出了这里,unique_lock<mutex>的生命周期就结束了
		//_mtx.unlock();

		return _p;
	}

	// 在这个情况下,拷贝构造也要禁掉
	Singleton(const Singleton& s) = delete;

	// 释放资源
	static void DelInstance()
	{
		unique_lock<mutex> lock(_mtx);
		delete _p;
		_p = nullptr;
	}

private:
	Singleton()
	{}

	// 需要再创建一个变量,来控制对象只能存在一个
	// 其实就是弄一个所有对象共用的变量,只要该变量标记了,就说明已经有一个对象了,那就不能再调用构造函数了
	static Singleton* _p; 
	// _p是一个临界资源,要保护它
	static mutex _mtx; //为了线程安全,加一个锁
};

Singleton* Singleton::_p = nullptr; //初始化
mutex Singleton::_mtx; //定义, 类里面的只是声明

如果想程序在结束之后,自动释放单例对象,可以引入尝试下面这个代码的思路

// 释放资源
	static void DelInstance()
	{
		// 这种思路下,不需要加锁了。因为_mtx锁可能已经释放了
        // 并且main函数都结束了,主线程结束了,其他线程肯定都结束了,不存在多线程的场景了
		delete _p;
		_p = nullptr;
	}

// 如果手动释放单例对象,可以手动调用DelInstance()
// 如果想在程序结束之后自动释放单例对象,可以引入一个小机制,利用生命周期
class GC
{
public:
	~GC()
	{
		Singleton::DelInstance();
	}
};

// 这里定义一个gc的静态变量,在当前程序结束之后,就会调用DelInstance()来释放单例对象
static GC gc; 
2.5.2饿汉模式实现
	class Singleton
	{
	public:
		static Singleton* GetInstance()
		{
			return &_inst;
		}
		
		Singleton(const Singleton& s) = delete;

	private:
		Singleton()
		{}

		static Singleton _inst;
	};

	Singleton Singleton::_inst; //在main函数之前就在静态区把唯一的对象创建好了, 不存在线程安全的问题了

2.5.3懒汉的饿汉的区别
  • 懒汉模式需要考虑线程安全和内存泄漏的问题,实现相对更复杂。饿汉就不需要考虑这些问题,实现起来也相对简单、

  • 懒汉模式是在需要的时候才创建对象并初始化,相对来说不会影响程序的进行,而饿汉模式是一开始就创建对象并初始化,如果代码量大,就会导致程序运行缓慢,影响用户体验

  • 饿汉模式无法保证对象的创建顺序,比如有多个单例类,并且有依赖关系(B依赖A), 要求先创建A,在创建B,那就不能用饿汉模式,要用懒汉

  • 如果在构造函数中有动态库链接,和创建线程(要链接线程库),那么就不能用饿汉模式。因为饿汉在main函数之前就初始化了,这个时候不知道库是否链接了,如果没有链接就会崩

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

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

相关文章

Android 手写签名功能详解:从原理到实践

Android 手写签名功能详解 1. 引言2. 手写签名核心实现&#xff1a;SignatureView 类3. 交互层实现&#xff1a;MainActivity 类4. 布局与配置5. 性能优化与扩展方向 1. 引言 在电子政务、金融服务等移动应用场景中&#xff0c;手写签名功能已成为提升用户体验与业务合规性的关…

Level2.8蛇与海龟(游戏)

#小龟快跑游戏 输入难度(1-5),蛇追到龟&#xff0c;游戏结束 #分析问题&#xff1a;从局部>整体 #游戏画面&#xff1a;创建画笔(海龟蛇)>1.海龟移动(键盘控制)>2.蛇(自动追踪&#xff0c;海龟位置)>3.海龟(限定范围&#xff0c;防止跑出画布之外)>4.游戏&…

【Android构建系统】如何在Camera Hal的Android.bp中选择性引用某个模块

背景描述 本篇文章是一个Android.bp中选择性引用某个模块的实例。 如果是Android.mk编译时期&#xff0c;在编译阶段通过某个条件判断是不是引用某个模块A, 是比较好实现的。Android15使用Android.bp构建后&#xff0c;要想在Android.bp中通过自定义的一个变量或者条件实现选…

【Canvas与诗词】醉里挑灯看剑 梦回吹角连营

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>醉里挑灯看剑梦回吹角连营 Draft1</title><style type"…

实现视频分片上传 OSS

访问 OSS 有两种方式&#xff0c;本文用到的是使用临时访问凭证上传到 OSS&#xff0c;不同语言版本的代码参考&#xff1a; 使用STS临时访问凭证访问OSS_对象存储(OSS)-阿里云帮助中心 1.安装并使用 首先我们要安装 OSS&#xff1a; npm install ali-oss --save 接着我们…

网络I/O学习(一)

一、什么是网络IO&#xff1f; 就是客户端和服务端之间的进行通信的通道(fd)。 二、网络IO通信步骤 1、建立套接字 int socketfd socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in servaddr; servaddr.sin_family AF_INET; servaddr.sin_addr.s_addr htonl(INADDR_A…

Selenium-Java版(css表达式)

css表达式 前言 根据 tag名、id、class 选择元素 tag名 #id .class 选择子元素和后代元素 定义 语法 根据属性选择 验证CSS Selector 组选择 按次序选择子节点 父元素的第n个子节点 父元素的倒数第n个子节点 父元素的第几个某类型的子节点 父元素的…

产品更新丨谷云科技 iPaaS 集成平台 V7.5 版本发布

五月&#xff0c;谷云科技 iPaaS 集成平台保持月度更新&#xff0c; V7.5 版本于近日正式发布。我们一起来看看新版本有哪些升级和优化。 核心新增功能&#xff1a;深化API治理&#xff0c;释放连接价值 API网关&#xff1a;全链路可控&#xff0c;精准管控业务状态 业务状态…

深度学习让鱼与熊掌兼得

通常,一个大的复杂的模型的loss会低,但是拟合方面不够,小的模型在拟合方面更好,但是loss高,我们可以通过深度学习来得到一个有着低loss的小模型 我们之前学过,peacewise linear可以用常数加上一堆这个阶梯型函数得到,然后因为peacewise linear可以逼近任何function,所以理论上…

TDuckX 2.6 正式发布|API 能力开放,核心表单逻辑重构,多项实用功能上线。

大家好&#xff0c;TDuckX 2.6 已正式发布。 本次更新以可集成性提升、数据处理能力增强和交互体验优化为核心&#xff0c;新增了包括 新增OpenAPI 模块、表单数据批量修改、字段导出分列 等多个面向开发者和实际业务落地场景的功能。 我们也重构了部分底层逻辑模块&#xff…

JAVA EE(进阶)_进阶的开端

别放弃浸透泪水的昨天&#xff0c;晨光已为明天掀开新篇 ——陳長生. ❀主页&#xff1a;陳長生.-CSDN博客❀ &#x1f4d5;上一篇&#xff1a;JAVA EE_HTTP-CSDN博客 1.什么是Java EE Java EE&#xff08;Java Pla…

ArcGIS Pro调用多期历史影像

一、访问World Imagery Wayback&#xff0c;基本在我国范围 如下图&#xff1a; 二、 放大到您感兴趣的区域 三、 查看影像版本信息 点击第二步的按钮后&#xff0c;便可跳转至World Imagery (Wayback 2025-04-24)的相关信息。 四 、点击上图影像版本信息&#xff0c;页面跳转…

组态王|组态王中如何添加西门子1200设备

哈喽,你好啊,我是雷工! 最近使用组态王采集设备数据,设备的控制器为西门子的1214CPU, 这里边实施边记录,以下为在组态王中添加西门子1200PLC的笔记。 1、新建 在组态王工程浏览器中选择【设备】→点击【新建】。 2、选择设备 和设备建立通讯要通过对应的设备驱动。 在…

6.2.2邻接表法-图的存储

知识总览&#xff1a; 为什么要用邻接表 因为邻接矩阵的空间复杂度高(O(n))&#xff0c;且不适合边少的稀疏图&#xff0c;所以有了邻接表 用代码表示顶点、图 声明顶点图信息 声明顶点用一维数组存储各个顶点的信息&#xff0c;一维数组字段包括2个&#xff0c;每个顶点的…

C++23 放宽范围适配器以允许仅移动类型(P2494R2)

文章目录 引言背景与动机提案内容与实现细节提案 P2494R2实现细节编译器支持 对开发者的影响提高灵活性简化代码向后兼容性 示例代码总结 引言 C23 标准中引入了许多重要的改进&#xff0c;其中一项值得关注的特性是放宽范围适配器&#xff08;range adaptors&#xff09;以允…

【技海登峰】Kafka漫谈系列(十一)SpringBoot整合Kafka之消费者Consumer

【技海登峰】Kafka漫谈系列(十一)SpringBoot整合Kafka之消费者Consumer spring-kafka官方文档: https://docs.spring.io/spring-kafka/docs/2.8.10/reference/pdf/spring-kafka-reference.pdf KafkaTemplate API: https://docs.spring.io/spring-kafka/api/org/springframe…

WebRTC技术下的EasyRTC音视频实时通话SDK,助力车载通信打造安全高效的智能出行体验

一、方案背景​ 随着智能交通与车联网技术的飞速发展&#xff0c;车载通信在提升行车安全、优化驾驶体验以及实现智能交通管理等方面发挥着越来越重要的作用。传统的车载通信方式在实时性、稳定性以及多媒体交互能力上存在一定局限&#xff0c;难以满足现代车载场景日益复杂的…

数据科学和机器学习的“看家兵器”——pandas模块 之二

目录 pandas 模块介绍 4.2 pandas 数据读取 4.2.1 课程目标 4.2.2 读取 Excel 文件中的数据 (一)读取某个工作表中的数据 (二)读取指定数据列的标签内容 (三)读取指定数据行的标签内容 (四)读取指定行或者列 4.2.3、读取 CSV 文件数据 4.2.4、课程总结回顾 4.2.5、课后…

MySQL--day2--基本的select语句

&#xff08;以下内容全部来自上述课程&#xff09; SQL概述 结构化查询语句 1. SQL分类 DDL&#xff1a;数据定义&#xff08;definition&#xff09;语言&#xff1a;create、drop、alter… DML&#xff1a;数据操作&#xff08;manipulation&#xff09;语言&#xff…

自动化:批量文件重命名

自动化&#xff1a;批量文件重命名 1、前言 2、效果图 3、源码 一、前言 今天来分享一款好玩的自动化脚&#xff1a;批量文件重命名 有时候呢&#xff0c;你的文件被下载下来文件名都是乱七八糟毫无规律&#xff0c;但是当时你下载的时候没办法重名或者你又不想另存为重新重…