C++语法(25)--- 异常与智能指针

news2024/11/25 0:30:15

C++语法(24) C++11_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/131054426?spm=1001.2014.3001.5501

1.异常

try
{
// 保护的标识代码
}catch( ExceptionName e1 )
{
// catch 块
}catch( ExceptionName e2 )
{
// catch 块
}catch( ExceptionName eN )
{
// catch 块
}

对于抛异常这个动作,其实是为了不让代码中断。要知道所谓的 

异常的抛出和匹配原则

1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
2. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)
4. catch(...)可以捕获任意类型的异常,问题是不知道异常错误是什么。
5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获,这个在实际中非常实用

在函数调用链中异常栈展开匹配原则

1. 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则
调到catch的地方进行处理。
2. 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。
3. 如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(...)捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止。
4. 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行

2.智能指针

1.引子

1.危害

1.当我们知道各种库函数都有抛异常的习惯后,就能知道其实new和delete也会出现所谓的异常抛出。但是此时的异常捕获会变的非常不容易。

2.当出现好几个连续的new时,我们还得i嵌套式的捕获异常很多层。这是因为当出现连续多个new时,我们不能保证每一个都能正常运行。如果是一个new 无所谓,出错直接抛出异常即可;但是面对两个时,第一个没有异常,等到第二个时出现异常,那么第二个理所当然不会new成功的,但是第一个的空间是已经开辟了,那么如果不嵌套式的delete空间,我们会出现内存泄漏。那如果是一片的new没有被delete,那么就会出现大片的内存泄露。

2.初次尝试解决

那么我们设计一个类型,该类型就用来存储对应new出来的空间。只要new开辟了空间,类就生成对象进行管理,那么我们通过对类的构造函数和析构函数的编写,使得资源的生命周期同对象的生命周期一致。

template<class T>
class SmartPtr
{
public:
	//RAII
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		delete _ptr;
	}

	//像指针一样
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	T& operator[](size_t pos)
	{
		return _ptr[pos];
	}

private:
	T* _ptr;
};

RAII:(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:1.不需要显式地释放资源。2.采用这种方式,对象所需的资源在其生命期内始终保持有效

像指针一样:保持了我们直接可以拿这个对象作为指针来使用。

3.当前的缺陷(悬空)

悬空:此时如果使用拷贝构造或者赋值拷贝时,新旧指针同时指向一片空间。也就意味着两个对象同时指向一个,那么此时如果对象的生命周期结束了,也就意味着该空间被释放两次,这在编译层面是不被允许的,所以此时需要改进。

2.auto_ptr

1.std::auto_ptr

C++98中就存在一个这样类似的智能指针

不过此时的智能指针存在一个问题:

1.作为用户的视角,智能指针也应该和指针一样,但我们同时拥有两个指针指向一个地址时,两个指针都应该能够使用

2.但是auto_ptr不同,它的实现是,一旦有新的智能指针赋值老的智能指针,老的智能指针就会被置空。

3.其实这样的设计会十分鸡肋。一来调换了个对象的名字,而没有将其实现的像真指针一样多个指针指向同一片空间;二来用户使用是可能会不知道老的智能指针已经被置空,而对智能指针进行操作,那么此时就会出现空指针的访问。这一点语法上面不被允许

#include<memory>
int main()
{
	std::auto_ptr<int> sp1(new int);
	std::auto_ptr<int> sp2(sp1); // 管理权转移

 // sp1悬空
	*sp2 = 10;
	cout << *sp2 << endl;
	cout << *sp1 << endl;
	return 0;
}

2.模拟实现auto_ptr

	template<class T>
	class auto_ptr
	{
	public:
		//RAII
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~auto_ptr()
		{
			delete _ptr;
		}

		auto_ptr(const auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
			sp._ptr = nullptr;
		}
		
		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
	};

该智能指针最好不要使用,由于其过于不合理

3.unique_ptr

1.std::unique_ptr

针对auto_ptr的漏洞,unique_ptr的解决方案就是将指针设置为唯一,不允许智能指针拷贝构造。

int mian()
{
	unique_ptr<int>up1(new int);
	unique_ptr<int>up2(up1);
	return 0;
}

2.模拟实现std::unique_ptr

其实之前就已经说明过不想使得默认函数生成的方法:

1.针对C++98就是将函数写到私有里:

在内部,默认函数可能还可以被间接使用,还是有风险的

private:
	unique_ptr(const unique_ptr<T>& sp)

2.指针C++11就是在函数后面加上 = delete表示不会生成默认函数:

unique_ptr(const unique_ptr<T>& sp) = delete;

3.shared_ptr

1.std::shared_ptr

该智能指针就符合我们之前觉得应该接近指针的实现,他能共享指针。

int mian()
{
	shared_ptr<int>up1(new int);
	shared_ptr<int>up2(up1);
	return 0;
}

不会出现悬空问题

2.模拟实现shared_ptr(引用计数)

如何引用计数也是一个问题,我们提出几种方案进行比较

1.在类的私有中追加一个计数

private:
	T* _ptr;
	int count;

这样是不可以的,首先我们需要清楚如果两个指针指向同一个位置,那么我们势必需要一个空间让两个对象同时看到。但是如果我们这样写,那么一个对象就有一个count,这样就十分荒唐了,我们到底的计数会十分混乱

2.在类的私有中追加静态计数

private:
	T* _ptr;
	static int count;

要知道,static在类中使用,那么整个类都共享了。如果我们指向的指针是不同的空间,但是得到的计数却是共享的,那么也就无法分辨什么时候释放哪个指针指向的空间了。

3.引用计数

private:
	T* _ptr;
	int* _pcount;

传入指针

	template<class T>
	class shared_ptr
	{
	public:
		//RAII
		shared_ptr(T* ptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{}

		~shared_ptr()
		{
			release();
		}

		void release()
		{
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;
			}
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			
			++(*_pcount);
		}
		
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			
			if(sp._ptr!=_ptr)
			{
				release();
				_ptr = sp._ptr;
				_pcount=(sp._pcount);
				++(*_pcount);
			}
			return *this;
		}

        int use_count()
		{
			return *_pcount;
		}

		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
		int* _pcount;
	};

3.问题

由于智能指针涉及到公共资源,难免会在多线程下使用,那么此时就会出现线程安全问题。

	void test_share_ptr()
	{
		shared_ptr<int> sp1(new int(1));

		thread t1([&]()
		{
				for (int i = 0; i < 10000; i++)
				{
					shared_ptr<int> sp2(sp1);
				}
		});

		thread t2([&]()
		{
				for (int i = 0; i < 10000; i++)
				{
					shared_ptr<int> sp3(sp1);
				}
		});

		t1.join();
		t2.join();

		cout << sp1.use_count() << endl;
	}

4.加锁

1.此时智能指针的设计就需要加锁来保护线程安全

2.不过,对于我们而言,放在类的对象中的锁这个做法是行不通的,因为这样构造出来的指针明明指向的是同一个结构,但是所谓的锁不是同一把锁,那么就算是加锁这个操作也是没有意义的

3.基于2的问题,我们需要的是不同对象拥有同一把锁,那么做法其实加入计数器的做法一样,都传入的是其指针

shared_ptr(T* ptr)
	:_ptr(ptr)
	,_pcount(new int(1))
	,_pmtx(new mutex)
{}

void release()
{
	bool flag = false;
	_pmtx->lock();
	if (--(*_pcount) == 0)
	{
		delete _ptr;
		delete _pcount;
		flag = true;
	}
	_pmtx->unlock();
	if (flag)
	{
		delete _pmtx;
	}
}

shared_ptr(const shared_ptr<T>& sp)
	:_ptr(sp._ptr)
	, _pcount(sp._pcount)
	, _pmtx(sp._pmtx)
{
	_pmtx->lock();
	++(*_pcount);
	_pmtx->unlock();
}

shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
			
	if(sp._ptr!=_ptr)
	{
		release();
		_ptr = sp._ptr;
		_pcount=(sp._pcount);
		_pmtx->lock();
		++(*_pcount);
		_pmtx->unlock();
	}
	return *this;
}

5.分析

1.之所以需要将计数器加锁是因为其开辟在堆区,堆区对于所有线程都共享,也就是说malloc得到的所有东西和free释放的东西都有风险。

2.但是release中判断变量flag并不需要被加锁,因为其实它是临时变量,临时变量存储在栈上,也就意味着所有的线程都有自己的独立栈结构来存储这一临时变量,所以不需要被保护

3.这样设计过的智能指针本身是线程安全的,它的安全代表着引用计数的加减操作是线程安全的,但是不代表智能指针指向的数据是线程安全的。这些数据并不是在智能指针内部执行的,所以这样的设计是无法保证数据线程安全的。需要在外面进行自行加锁使得线程安全。

6.循环引用问题

struct ListNode
{
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
	shared_ptr<ListNode> _next;
	shared_ptr<ListNode> _prev;
};

void test_share_ptr2()
{
	std::shared_ptr<ListNode> n1(new ListNode);
	std::shared_ptr<ListNode> n2(new ListNode);

	n1->_next = n2;
	n2->_prev = n1;
    cout << n1.use_count() << " " << n2.use_count() << endl;
}

1.创建两个ListNode的智能指针是为了让其能自动释放内存,但是此时依然出现了内存泄露的问题

2.之所以出现是因为:当前n1指针和n2指针其实是临时变量,出了作用域就会调用析构函数销毁,但是由于其是智能指针管理的,所以当前的问题在于n1的next和n2的prev。n1的next指向n2,那么n2的彻底销毁需要n1的销毁;n2的prev指向n1,那么n1的彻底销毁需要n2的销毁。就出现了内存泄漏的问题。

3.我们最终得到的结果证明了,确实两个智能指针的count都为2

4.weak_ptr

weak_ptr:可以指向资源,也可以访问资源;但是它不增加引用计数

struct ListNode
{
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
	std::weak_ptr<ListNode> _next;
	std::weak_ptr<ListNode> _prev;
};

void test_share_ptr2()
{
	std::shared_ptr<ListNode> n1(new ListNode);
	std::shared_ptr<ListNode> n2(new ListNode);
	n1->_next = n2;
	n2->_prev = n1;
	cout << n1.use_count() << " " << n2.use_count() << endl;
}

得到的结果:n1和n2的count都是1,表面二者的计数器只计数了一次

模拟实现

	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
			return *this;
		}

		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

	public:
		T* _ptr;
	};

3.定制删除器

C++库实现

默认的删除其实只是针对指针,但是如果我们构造的智能指针指向一个特定的结构体,也就意味着我们不能直接删除掉智能指针就一了百了。所以诞生了定制删除器,在库中的智能指针在构造时会传入所谓的删除器,随后传入内部自动删除指定结构体的内存,防止内存泄漏。

template<class T>
struct DeleteArray
{
	void operator()(const T* ptr)
	{
		delete[] ptr;
	}
};

int main()
{
	//MY::test_share_ptr1();
	//MY::test_share_ptr2();
	std::shared_ptr<int> sp1(new int[10], DeleteArray<int>());
	std::shared_ptr<string> sp2(new string[10], DeleteArray<string>());
	std::shared_ptr<string> sp3(new string[10], [](string* ptr) {delete[] ptr; });
	std::shared_ptr<FILE> sp4(fopen("Test.cpp", "r"), [](FILE* ptr) {fclose(ptr); });
	return 0;
}

模拟

	template<class T>
	class default_delete
	{
	public:
		void operator()(T* ptr)
		{
			delete ptr;
		}
	};

	template<class T, class D = default_delete<T>>
	class shared_ptr
	{
	public:
		void release()
		{
			bool flag = false;
			_pmtx->lock();
			if (--(*_pcount) == 0)
			{
				//delete _ptr;
				_del(_ptr);
				delete _pcount;
				flag = true;
			}
			_pmtx->unlock();
			if (flag)
			{
				delete _pmtx;
			}
		}

	private:
		T* _ptr;
		int* _pcount;
		mutex* _pmtx;

		D _del;
	};

缺点

MY::shared_ptr < FILE, decltype([](FILE* ptr) {fclose(ptr); }) > n2(fopen("Text.cpp", "r"));

我们设计的模板传入的是函数,但是所谓的lambda出来的是函数类型,类型和函数不是同一个性质的东西,所以无法运行。

使用仿函数

template<class T>
struct Fclose
{
	void operator()(const T* ptr)
	{
		fclose(ptr);
	}
};

MY::shared_ptr < FILE, Fclose<FILE>> n2(fopen("Text.cpp", "r"));

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

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

相关文章

RT-Thread qemu mps2-an385 bsp 移植制作 :BSP 制作篇

下载 V2M-MPS2_CMx_BSP mps2 的资料很少&#xff0c;所以唯一能下载的是 ARM 官方的 V2M-MPS2_CMx_BSP&#xff0c;下载地址为&#xff1a; https://keilpack.azureedge.net/pack/Keil.V2M-MPS2_CMx_BSP.1.8.0.pack 其实这是个 Keil MDK5 的 Pack 包&#xff0c;安装后&#x…

JAVA-ReentrantLock(五)

概念 在Java中&#xff0c;“lock”&#xff08;锁&#xff09;是一种用于并发控制的机制。它用于确保在多线程环境中&#xff0c;同一时刻只有一个线程可以访问共享资源或临界区。当一个线程获得了锁&#xff0c;其他线程将被阻塞&#xff0c;直到持有锁的线程释放它。这样可…

Cocos Creator 3.8 后期效果 Shader 编写(1/2) 基础篇

原文链接&#xff1a;Cocos Creator 3.8 后期效果 Shader 编写&#xff08;1/2&#xff09; 基础篇 在 Cocos Creator 3.8 版本中&#xff0c;新增了不少实用的特性&#xff0c;其中我最喜欢的&#xff0c;就是它自带后期效果管线&#xff0c;并且还内置了许多高级效果。 有用…

XUbuntu22.04之Linux剪切板和selection primary区域(一百八十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

二、Java框架基础02 XML

二、XML 2.1 XML 简介 XML 即可扩展标记语言&#xff0c;一种简单的数据存储语言&#xff0c;使用一系列简单的标记来描述结构化数据 XML 的特点 XML 与操作系统&#xff0c;编程语言的开发平台无关规范统一&#xff0c;实现不同系统之间的数据交互 2.1.1 XML 的文档结构 以下…

vulnhub靶场之CengBox3

1.信息收集 输入命令&#xff1a;netdiscover -i eth0 -r 192.168.239.0 &#xff0c;发现181机器存活 输入命令nmap -p- -sV -O -Pn -A 192.168.239.181 &#xff0c;进行端口探测&#xff0c;发现存在22、80、443端口&#xff0c;还发现存在域名ceng-company.vm。 将域名c…

【linux基础】05-linux文件系统

概述 在Linux中,文件系统是一种分层结构,它将文件和目录组织成树状结构。文件系统从“根”目录开始,该目录由单个正斜杠(“/”)表示。 如下图所示: Linux 支持多种类型的文件系统,包括: Ext4:这是大多数 Linux 发行版的默认文件系统。它是一个日志文件系统,提供良…

拉格朗日乘数法(Lagrange)的推导

同济版高数上&#xff0c;关于拉格朗日乘数法&#xff0c;以及好多知识点说的语焉不详、模棱两可&#xff0c;在阅读了知乎等博主的几篇文章后&#xff0c;才算勉强弄懂了该知识的原理。 首先说一下高数上隐函数求导。所谓的隐函数求导&#xff0c;就是在方程中多个变量之间的…

如何使用Java 实现excel模板导出---多sheet导出?

实现多个sheet的excel导出功能 效果展示&#xff1a; maven依赖 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.17</version></dependency><dependency><groupId>or…

泛微E-Cology XXE漏洞复现(QVD-2023-16177)

0x01 产品简介 泛微协同管理应用平台E-Cology是一套兼具企业信息门户、知识文档管理、工作流程管理、人力资源管理、客户关系管理、项目管理、财务管理、资产管理、供应链管理、数据中心功能的企业大型协同管理平台。 0x02 漏洞概述 泛微e-cology某处功能点最初针对用户输入的…

STM32 点灯实现 7.18

嵌入式&#xff1a; 以应用为中心&#xff0c;以专用计算机为基础&#xff0c;软硬件可裁剪ARM A系列芯片&#xff1a;高端芯片&#xff0c;实现人机互动 R系列&#xff1a;实现时效性 M系列&#xff1a;低端芯片&#xff0c;控制硬件设备&#xff0c;灯&#xff0c;风扇....…

Springboot初识(一)

一.什么是Spring Boot Spring Boot是一个开源的、用于简化Spring应用程序开发的框架。它是Spring项目的一个子项目&#xff0c;旨在为Spring应用程序提供更快速、更便捷的开发体验。Spring Boot基于Spring框架&#xff0c;同时也整合了其他Spring项目和第三方库&#xff0c;使…

Unity-AssetBundle

一、AB 包介绍 ​ AB 包是特定于平台的资源压缩包&#xff0c;类似于压缩文件。其中资源可包括&#xff1a;模型、贴图、预设体、音效、材质球等等。 ​ 相较于 Resources 文件夹下的资源文件&#xff0c;AB 包能够更好管理资源&#xff1a; Resources 文件夹&#xff1a;打包…

【设计模式】23种设计模式——建造者模式Builder(原理讲解+应用场景介绍+案例介绍+Java代码实现)

介绍 建造者模式又叫生成器模式&#xff0c;是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别)&#xff0c;使这个抽象过程的不同实现方法可以构造出不同属性的对象建造者模式是一步一步创建一个复杂的对象&#xff0c;它允许用户只通过指定复杂对象的类型和…

【PHP面试题79】在Linux中如何设置MySQL和PHP服务开机启动

文章目录 &#x1f680;一、前言&#x1f680;二、设置MySQL服务开机启动&#x1f50e;2.1 打开终端&#x1f50e;2.2 编辑MySQL配置文件&#x1f50e;2.3 修改配置文件&#x1f50e;2.4 检查MySQL服务是否已启动&#x1f50e;2.5 设置MySQL服务开机启动 &#x1f680;三、设置…

C# Modbus通信从入门到精通(12)——Modbus ASCII协议原理

Modbus ASCII是串行链路上的协议,也就是说ModbusASCII是通过串口通信来实现的,它可以通过RS232、RS485物理层的接口来实现,同时它也是一个主从协议,在同一时间总线上只能有一个主站和一个或多个(最多247)个从站。Modbus通信总是由主站发起,从站没有接收到主站的请求时不…

NOAA国家强风暴实验室的天气雷达研究历史(1962年~2016年)

一、1962年-NSSP开始研究WSR-57 美国气象局国家严重风暴项目(NSSP)的一小群研究人员从堪萨斯城搬到俄克拉荷马州诺曼的天气雷达实验室,并开始研究最近安装的研究天气监视雷达-1957(WSR-57)。 二、1964年-NSSL开发的脉冲多普勒雷达技术 1956年,康奈尔航空实验室建造了一…

C++OpenCV(2):图像处理基础概念与操作

&#x1f506; 文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 &#x1f506; OpenCV项目地址及源代码&#xff1a;点击这里 文章目录 图形读取与显示加载图片显示图片打印图片信息保存图片 色彩模型转换RGB颜色模型HSV颜色模型HLS模型LAB模型 图像像素读写操作像素算数运…

python机器学习(二)特征工程、K-近邻算法、KNN工作流程、scikit-learn实现K近邻算法、K值选择、距离计算、KD树

特征工程 把特征转换为机器容易识别的数据&#xff0c;把特征a转化为机器容易读懂、量化的语言 归一化Min-Max 将原始数据映射到[0,1]之间 X ′ x − m i n m a x − m i n X \frac{x-min}{max-min} X′max−minx−min​ 但是归一化是有弊端的&#xff0c;比如有一个值错误…

OJ练习第142题——路径总和 II

113. 路径总和 II 力扣链接&#xff1a;113. 路径总和 II 题目描述 给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 叶子节点 是指没有子节点的节点。 示例 Java代码&#xff08;深度优先搜…