C++异常智能指针

news2025/1/19 19:51:14

目录

异常

异常的定义

异常的抛出和捕获

异常安全问题

异常的规范

智能指针

RAII思想

使用RAII的例子

智能指针

文件资源

在linux中管理锁资源

智能指针发展历程

auto_ptr

unique_ptr

shared_ptr


异常

异常的定义

异常是一种处理运行时错误的机制,它允许程序在发生错误时能够以一种可预测和可控的方式响应,而不是使程序直接崩溃。异常是程序运行时抛出的一个信号,表明程序中出现了某种特殊的情况,需要被处理。

异常的抛出和捕获

异常的抛出:抛异常是对程序发生错误的预测,当程序猿自己都不知道自己的代码错误的情况时,异常也就无从抛出了。所以异常抛出的位置是由程序猿主观设定的,使用异常处理来确保程序的健壮性和可维护性。

语法:将可能抛出异常的代码块用try包裹,在try块内部throw 异常对象

异常的捕获:将抛出的异常在距离最近的且类型最匹配的catch块内进行捕获,对程序中出现某种异常情况进行处理。如果当前栈并没有捕获逻辑,则销毁当前栈继续向上寻找可以匹配的catch块来捕获,若到main函数栈依旧没有匹配的,则程序崩溃

语法:使用类似形参的方式接受异常对象,将处理异常的方式定义在catch块内部。

通常情况,可以将异常类设计为父子关系,具体的异常类都是继承自一个基类exception类,我们可以使用exception类形参来接受抛出的任何的异常对象,调用成员函数what()可以得知某个异常的具体描述,这也是多态的例子。

异常安全问题

异常安全问题是由于异常在抛出时,会停止执行当前try块的代码,寻找最近的最匹配的catch块进行捕获,这就使得某些释放资源的代码可能不会被执行,造成资源的泄露

举个栗子:

总结:这样的情况下必会造成资源泄露。

try{
  资源申请;
  异常的抛出;
  资源释放;
}
catch(exception& e)
{
  捕获逻辑;
}

资源泄露的种类:new delete内存泄露、open close文件描述符泄露、lock unlock死锁... 

C++中如何解决异常安全问题:

使用RAII思想,让对象管理资源,栈帧销毁时自动调用析构释放申请的资源。

异常的规范

异常的使用规范是为了让编程者知道哪些函数是否会抛出异常,会抛出什么类型的异常。当然也可以不遵守这个规范,但是写一份易维护的代码是编写者需要慢慢养成的素养。

void func() throw(异常类型A,异常类型B,异常类型C); //表明此函数可能抛出A,B,C三种类型的异常

void pow() throw(异常类型A); //表明此函数只可能抛出A这一种类型的异常

void run() throw(); //表明此函数不会抛出异常

智能指针

RAII思想

RAII(Resource Acquisition Is Initialization资源获取即初始化),是一种将资源的生命周期和对象的生命周期绑定在一起的思想,从而消除资源泄露的问题。

即可以让栈上的对象来管理申请的资源(堆空间、套接字、文件描述符、锁...),在对象出作用域时自动调用析构函数将申请的资源释放

使用RAII的例子

智能指针

智能指针就使用了这种思想,下面实现一个最简单的智能指针:

template<class T>
class SmartPtr //使用类来封装资源
{
public:
	SmartPtr(T* ptr):_ptr(ptr)
	{}
	~SmartPtr()
	{
		delete _ptr;
		cout << "delete->" << _ptr << endl;
	}
private:
	T* _ptr;//被管理的堆空间指针
};
int main()
{
	SmartPtr<int> ptr(new int(2));//ptr对象的生命周期和堆资源的生命周期绑定了
	return 0;
}

这样简单的智能指针,在对象析构时会自动调用delete释放申请的堆空间。这样就消除了忘记释放堆空间而导致内存泄露的危害。


文件资源

同样使用RAII思想还可以管理其他需要手动释放的资源,比如打开的文件:

#include<iostream>
#include<fstream>
using namespace std;
class SmartFile
{
public:
	SmartFile(const char* path)
	{
		_filehandle.open(path, ios::out);
	}
	fstream& gethandle()
	{
		return _filehandle;//获取句柄,方便对文件进行操作
	}
	~SmartFile()
	{
		_filehandle.close();//对象生命周期结束自动关闭文件
		cout << "file closed" << endl;
	}
private:
	fstream _filehandle;//文件句柄
};

int main()
{
	SmartFile file("1.txt");
	char buf[] = "sas";
	file.gethandle().write(buf,strlen(buf));
}

在linux中管理锁资源
#include<pthread.h>
class mutexguard
{
public:
    mutexguard(pthread_mutex_t * mutex) :_mutex(mutex)
	{
		pthread_mutex_lock(_mutex);//加锁
	}
    ~mutexguard()
    {
		pthread_mutex_unlock(_mutex);//解锁
    }
private:
	pthread_mutex_t * _mutex;
};
int main()
{
	pthread_mutex_t _mutex;
	pthread_mutex_init(&_mutex, nullptr);
	mutexguard(&mutex);
	return 0;
}

上面所实现的最简单的智能指针存在拷贝和赋值的问题,例如:

int main()
{
	SmartPtr<int> ptr1(new int(2));
	SmartPtr<int> ptr2 = ptr1;//拷贝给ptr2,让ptr2也可以访问同一个指针
	return 0;
}

此时程序一旦运行,立马崩溃

原因是此时智能指针这个类没有实现拷贝构造函数,只有默认的拷贝构造:对内置类型按字节浅拷贝。使得ptr1和ptr2内管理的指针指向同一块堆空间

当出作用域时,两个对象都调用析构函数,使得同一块堆空间被释放两次,程序崩溃。


如何解决这个问题?C++的发展过程出现了多种解决方式如下:

智能指针发展历程

auto_ptr

初代智能指针,当拷贝智能指针时,会将智能指针的所有权转移。

auto_ptr<int> ptr1(new int(2));//ptr1->new int(2)
auto_ptr<int> ptr2=ptr1; //ptr2->new int(2)  ptr1->nullptr

缺点

没有解决需求:如果有人想同时使用ptr1,ptr2管理同一指针是无法做到的,且如果不了解管理权转移的特性还会出现访问空指针的错误。 

不支持数组:auto_ptr默认指向的是一个对象,如果指向的是一块由多个对象组成的连续堆空间,释放时智能释放第一个对象。(定制删除器来解决:传入可调用对象来释放空间)

unique_ptr

它是c++11引用的一个智能指针:它拥有其指向对象的独占所有权,即只可以有一个智能指针指向要管理的指针(暴力方法)。通过私有拷贝构造、赋值函数来禁止unique_ptr的拷贝和赋值

缺点:还是没有解决同时用多个个智能指针管理同一个指针的需求。

shared_ptr

使用引用计数的思想来实现,解决了用多个智能指针管理同一指针的需求。

引用计数:使用计数器来统计指向资源的指针个数,新增指针指向时,计数器++,有指针移除时,计数器--,如果计数器减到0,表明已经没有指针指向这块资源了,即马上将资源释放

下面实现一个简单的shared_ptr

template<class T>
class shared_ptr
{
public:
	shared_ptr(T* ptr) :_ptr(ptr), _cnt(new int(1))//新的智能指针指向新资源,默认计数器为1
	{}
	shared_ptr(const shared_ptr& sp)//拷贝构造
	{
		//指向相同,计数器++
		_ptr = sp._ptr;
		_cnt = sp._cnt;//两智能指针使用同一计数器
		(*_cnt)++;
	}
	void del()
	{
		if (--(*_cnt) == 0)//智能指针解除对一个堆空间指向时,要判断计数器是否为0,为0才delete
		{
			cout << "delete->" << _ptr << endl;
			delete _ptr;
            delete _cnt;
		}
	}
	shared_ptr& operator=(const shared_ptr& sp)//赋值运算符重载
	{
		if (sp._ptr != _ptr)//保证指向相同的智能指针不会给自己赋值
		{
			del();//左边指向资源指针--
			_ptr = sp._ptr;
			_cnt = sp._cnt;
			*(_cnt)++;//右边指向资源指针++
		}
		return *this;
	}
	~shared_ptr()
	{
		del();
	}
	//重载*,->使智能指针像指针一样使用
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;//所管理的指针
	int* _cnt;//计数器
};

使用以下代码测试 

 结果:

shared_ptr设计非常优雅,可用性强,但在实际开发过程中发现了另一问题:循环引用

循环引用的分析和解决:

循环引用是指管理一块堆空间的智能指针存在于另一块堆空间,依赖于另一块堆空间的释放这块堆空间才会释放;而管理另一块堆空间的智能指针又在这一块堆空间上,依赖于这一块堆空间的释放。

一旦形成这种关系,两块堆空间都将无法释放,造成内存泄露!

解决方法:在堆内部创建智能指针shared_ptr时,改为创建weak_ptrweak_ptr只有指向的作用,不会占用引用计数而导致堆空间无法释放

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

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

相关文章

20240629在飞凌开发板OK3588-C上使用Rockchip原厂的SDK跑通I2C扩展GPIO芯片TCA6424ARGJRR

20240629在飞凌开发板OK3588-C上使用Rockchip原厂的SDK跑通I2C扩展GPIO芯片TCA6424ARGJRR 2024/6/29 18:02 1、替换DTS了&#xff1a; Z:\repo_RK3588_Buildroot20240508\kernel\arch\arm64\boot\dts\rockchip viewproviewpro-ThinkBook-16-G5-IRH:~/repo_RK3588_Buildroot2024…

Jedis、Lettuce、RedisTemplate连接中间件

jedis就像jdbc一样&#xff0c;用于两个端直接的连接。 1.创建Spring项目 这里不过多赘述... 2.导入连接工具jedis 在pom文件中导入jedis的依赖。 <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version&…

【雷达原理】雷达测角原理及实现方法

目录 一、雷达测角原理1.1 测角研究历史和现状1.2 测角方法总结1.3 3DFFT测角1.3.1 基本原理1.2.2 测角性能 二、MATLAB仿真案例参考文献 一、雷达测角原理 1.1 测角研究历史和现状 &#xff08;1&#xff09;早期采用窄波束对准目标&#xff0c;目标的角度对应于天线的角度读…

Unity3D 转换微信小游戏指引 01

Unity3D 转换微信小游戏指引系列&#xff08;第一期&#xff09; 搭建环境 安装 WebGL 模块 首先&#xff0c;Unity 要安装WebGL Build Support模块。 在 Hub 找到安装的引擎&#xff0c;点击设置按钮&#xff0c;点击Add modules添加模块。 勾选WebGL Build Support&#…

股指期权交割日期是什么时候?在每个月几号?

今天带你了解股指期权交割日期是什么时候&#xff1f;在每个月几号&#xff1f;期权交割日是指合约到期之日&#xff0c;即投资者需要履行买卖合约的义务。 股指期权的交割日期通常是期权合约到期日的第三个星期五。如果这一天是公共假日&#xff0c;则交割日可能会提前到前一…

检索增强生成RAG系列3--RAG优化之文档处理

在上一章中罗列了对RAG准确度的几个重要关键点&#xff0c;主要包括2方面&#xff0c;这一章就针对其中一方面&#xff0c;来做详细的讲解以及其解决方案。 目录 1 文档解析1.1 文档解析工具1.2 实战经验1.3 代码演示 2 文档分块2.1 分块算法2.2 实战经验2.3 代码演示 3 文档e…

Workerman在线客服系统源码,附搭建教程

源码介绍&#xff1a; Workerman在线客服系统源码。 workerman是一个高性能的PHP socket 服务器框架&#xff0c;workerman基于PHP多进程以及libevent事件轮询库&#xff0c;PHP开发者只要实现一两个接口&#xff0c;便可以开发出自己的网络应用&#xff0c;例如Rpc服务、聊天…

基于Java的会员制医疗预约服务管理信息系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Java技术ssm框架&#xff0c;结合JSPM工作流引擎 工具&#xff1a;IDEA/Eclipse、Navicat、Maven …

每日一题——Python实现PAT乙级1090 危险品装箱(举一反三+思想解读+逐步优化)4千字好文

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 题目链接&#xff1a;https://pintia.cn/problem-sets/994805260223102976/exam/problems/typ…

2024 vue3入门教程:windows系统下部署node环境

一、打开下载的node官网 Node.js — 下载 Node.js 二、根据个人喜好的下载方法&#xff0c;下载到自己的电脑盘符下 三、我用的是方法3下载的压缩包&#xff0c;解压到E盘nodejs目录下&#xff08;看个人&#xff09; 四、配置电脑的环境变量&#xff0c;新建环境变量的时候…

树莓派4B学习笔记17:RBG_LED全色域灯的驱动模块编写

今日继续学习树莓派4B 4G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: ​ Python 版本3.7.3&#xff1a; ​ 今日学习&#xff1a;RBG_LED全色域灯的驱动模块编写…

初学者轻松搞定19个经典的Python程序以及代码演示

Python的经典程序展示了Python语言基本特性和功能的简单示例,这些程序在学习和理解Python编程语言的过程中起着至关重要的作用. 一些常见的经典Python程序及其在学习Python时的功能&#xff1a; 1.Hello, World! print("Hello, World!")解释:这是Python的基本输出…

(单机架设教程)凯旋|当年的QQ游戏

前言 今天给大家带来一款单机游戏的架设&#xff1a;凯旋 &#xff0c; 当年的QQ游戏 如今市面上的资源参差不齐&#xff0c;大部分的都不能运行&#xff0c;本人亲自测试&#xff0c;运行视频如下&#xff1a; 凯旋单机 搭建教程 此游戏架设需要安装虚拟机&#xff0c; 没有…

【面试系列】Python 高频面试题

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;欢迎订阅相关专栏&#xff1a; ⭐️ 全网最全IT互联网公司面试宝典&#xff1a;收集整理全网各大IT互联网公司技术、项目、HR面试真题. ⭐️ AIGC时代的创新与未来&#xff1a;详细讲解AIGC的概念、核心技术、…

基于源码详解ThreadPoolExecutor实现原理

个人博客地址 基于源码详解ThreadPoolExecutor实现原理 | iwts’s blog 内容拆分 这里算是一个总集&#xff0c;内容太多&#xff0c;拆分成几个比较重要的小的模块&#xff1a; ThreadPoolExecutor基于ctl变量的声明周期管理 | iwts’s blog ThreadPoolExecutor 工作线程…

【PL理论深化】(13) 变量与环境:文法结构 | 真假表达式:isZero E | let 表达式叠放 | 定义的规则 | 条件语句的使用

&#x1f4ac; 写在前面&#xff1a;从现在开始&#xff0c;让我们正式设计和实现编程语言。首先&#xff0c;让我们扩展在之前定义的整数表达式语言&#xff0c;以便可以使用变量和条件表达式。 目录 0x00 文法结构 0x01 真假表达式&#xff1a;isZero E 0x02 let 表达式叠…

1-5题查询 - 高频 SQL 50 题基础版

目录 1. 相关知识点2. 例题2.1.可回收且低脂的产品2.2.寻找用户推荐人2.3.大的国家2.4. 文章浏览 I2.5. 无效的推文 1. 相关知识点 sql判断&#xff0c;不包含null&#xff0c;判断不出来distinct是通过查询的结果来去除重复记录ASC升序计算字符长度 CHAR_LENGTH() 或 LENGTH(…

LeetCode-Leetcode 1120:子树的最大平均值

LeetCode-Leetcode 1120&#xff1a;子树的最大平均值 题目描述&#xff1a;解题思路一&#xff1a;递归解题思路二&#xff1a;0解题思路三&#xff1a;0 题目描述&#xff1a; 给你一棵二叉树的根节点 root&#xff0c;找出这棵树的 每一棵 子树的 平均值 中的 最大 值。 子…

文章浮现之单细胞VDJ的柱状图

应各位老师的需求复现一篇文章的中的某个图 具体复现图5的整个思路图&#xff0c;这里没有原始数据&#xff0c;所以我使用虚拟生产的metadata进行画图 不废话直接上代码&#xff0c;先上python的代码的结果图 import matplotlib.pyplot as plt import numpy as np# 数据&#…

RocketMQ常用基本操作

文章中的rabbitmq使用的是rocketmq-all-5.1.3-bin-release版本&#xff0c;需要安装包的可自行下载 RockerMQ启动停止命令 启动命令 nohup sh bin/mqnamesrv & nohup sh bin/mqbroker -n localhost:9876 --enable-proxy & 查看日志 tail -f ~/logs/rocketmqlogs/…