异常c/c++

目录

1.c语言传统处理错误方式

1、终止程序

2、返回错误码

2.c++异常概念

3.异常的使用

 3.1异常的抛出与捕获

3.2异常安全(还有一些异常重新抛出)

3.3异常规范

4.自定义异常体系

5.c++标准库的异常体系

6.异常优缺点

 1、优点

2、缺点

7、补充


1.c语言传统处理错误方式

1、终止程序

如assert断言、内存错误(原因有很多,越界访问啊等等)、除0错误

2、返回错误码

错误码,常见的就是程序运行后,如果没有错误的话,就是返回0,不正常的时候返回负的多少或者其他奇怪的数字。很多库的接口函数都把错误码放到errno中,表示错误。遇到这种,错误的原因需要查表或者自行寻找到错误原因。

2.c++异常概念

 异常在很多面向对象的语言都有,python也有。

异常是一种处理错误的方式,可以理解为程度比assert轻一点,处理方式不是粗暴的终止程序。当出现某个编译器无法处理的错误时,可以将其作为异常抛出,由程序员预先设置的程序来处理这个错误。

throw:抛出异常,“throw 字符串或数字”

catch:捕获异常,比如捕获到了字符串或数字,就会进入对应的块域中,执行设定的语句。可以多个catch捕获多个异常(比如针对字符串的、针对数字的)

try:在try里面的语句,都会在运行时进行检查,如果遇到异常,把异常抛出后,会马上跳到catch中。

try
{
  // 保护的标识代码
}catch( ExceptionName e1 )
{
  // catch 块
}catch( ExceptionName e2 )
{
  // catch 块
}catch( ExceptionName eN )
{
  // catch 块
}
void fa() {
	int a; cin >> a;
	if (a == 2)
	{
		throw "wdawd";
	}
	else if (a == 3)
	{
		throw 4;
	}
	else if (a == 4)
	{
		string s("dwwdawda");
		throw s;
	}
	else if (a == 5)
	{
		stack<int>s;
		s.push(2);
		throw s;
	}
}

int main()
{
	try {
		fa();
	}
	catch (const char* err)
	{
		cout << err << endl;
	}
	catch (int x)
	{
		cout << x << endl;
	}
	catch (const string& es)
	{
		cout << es << endl;
	}
	catch (...) {
		cout << 231231 << endl;
	}
	return 0;
}

如果没有遇到异常,在执行完try里面的语句后,会直接跳过catch里面的内容。

注意,如果是库里的对象返回的话,建议用const 类型&来接受,因为这样如果这个对象有移动拷贝的话,这个写法可以接受右值也可以接受左值,提高效率。

3.异常的使用

 3.1异常的抛出与捕获

相比c语言传统的错误码(遇到错误,要层层返回,每层都要处理,直到返回main函数),异常可以直接跳回到匹配的catch处。这个过程中,跳过的函数都是会正常销毁的

异常抛出和匹配原则

1、异常是通过抛出对象引出的,该对象的类型决定了应该激活哪个catch

2、被选中的处理代码是调用链(就是从main到第一个函数到其内的函数再到其内的函数,一路下去)中与对象类型匹配且距离抛出异常的位置最近的,比如在第一个函数处也有try,然后执行try里面的内容时抛了异常,并且catch的内容与抛出的对象的类型是匹配的,那就会执行第一个函数处的对应catch而不是main函数处的catch。

3、catch(...)可以捕获任意类型的异常,问题是不知道异常是什么   用来兜底的,建议一定加

4、抛出异常后,对于相应的对象,会生成该对象的拷贝,因为这个对象可能是个临时对象(比如局部变量),这个拷贝出来的对象,在catch之后会销毁。(效率还是很高的,因为临时对象的话,走移动拷贝,效率挺高的)。比如我们抛出了一个string对象,就上面的代码中一样。

5、类型不一定严格匹配,可以抛出派生类对象,让基类捕获,利用多态。具体看下面的异常体系

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

1、throw抛出后,要确认自己是否在try里面,在的话才去找匹配的catch,然后跳到匹配的catch处

2、没有匹配的catch,就跳出当前的函数栈,在调用该函数的函数栈中继续找匹配的catch

3、如果在main函数中都没有找到匹配的catch就会终止程序。而整个沿着调用链向上找匹配的catch的过程,就是栈展开。所以实际中,我们都会加个catch(...),否则有异常没捕获就会直接终止程序。

4、在匹配的catch处执行完对应的语句后,会沿着当前的try ....catch之后的语句继续执行

3.2异常安全(还有一些异常重新抛出)

异常安全的核心问题就是在调用链非常长的情况下,异常会直接跳到catch,而这个过程很可能横跨了很多个函数。

1、不要在构造函数里抛异常,因为抛异常会直接跳到catch。如果构造函数里面要进行初始化,最典型的就是开辟多段空间给多个指针变量,如果这个过程中抛异常了,导致有些指针变量没有被初始化,仍然是野指针或空指针,那么析构函数在delete或者free的时候就会抛异常或者报错。简单点就是说可能会造成对象不完整或者没有完全初始化

2、析构函数不要抛异常。因为析构函数负责资源的清理,如果在析构函数中抛异常,很可能导致内存泄漏等问题,比如某个空间没有被delete掉。

3、异常经常会导致内存泄漏的问题。比如new和delete中间有一块抛出了异常,导致直接跳到catch了,delete就不会执行,这样就内存泄漏了,还会导致lock和unlock之间的死锁问题(这个是线程的知识,看我linux和网络的部分即可)

接下来是一种处理方法,利用异常重新抛出

void fa() {
	int a; cin >> a;
	if (a == 2)
	{
		throw "wdawd";
	}
	else if (a == 3)
	{
		throw 4;
	}
	else if (a == 4)
	{
		string s("dwwdawda");
		throw s;
	}
	else if (a == 5)
	{
		stack<int>s;
		s.push(2);
		throw s;
	}
}
void f() {
	int* x = new int;
	try {
		fa();
	}
	catch (...) {//不一定是...,只是最保险的就是这个,不管抛了什么异常都可以接受
		delete x;
		throw;//不管接受到什么异常,都通通重新抛出去。
	}
}
int main()
{
	try {
		f();
	}
	catch (const char* err)
	{
		cout << err << endl;
	}
	catch (int x)
	{
		cout << x << endl;
	}
	catch (const string& es)
	{
		cout << es << endl;
	}
	catch (...) {
		cout << 231231 << endl;
	}
	return 0;
}

核心思路就是在关键位置提前捕获,然后再重新抛出

比较恶心的情况:

void f() {
	int* x = new int;
    int *x1,x2;
	try {
		 x1 = new int;
		try {
			 x2 = new int;
		}
		catch (...) {
			delete x1;
			delete x;
			throw;
		}
	}
	catch (...) {
		delete x;
		throw;
	}
    delete x;
    delete x1;
    delete x2;
}

这个方法写起来太麻烦了,可见这个方法也不是很好,依靠智能指针可以更加的高效处理这个问题

class SmartPtr {
public:
	SmartPtr(int *ptr)
		:_ptr(ptr)
	{}
	~SmartPtr() {
		delete _ptr;
	}
private:
	int* _ptr;
};
void f() {
	SmartPtr s1(new int);
	SmartPtr s2(new int);
	SmartPtr s3(new int);
	//这样就可以依靠析构函数自动清理资源了。
	//就算抛了异常,s1、s2、s3是局部变量,在出了作用域会自动销毁,调用它的析构函数
	
}

3.3异常规范

出于上面的情况,98的时候,希望大家写异常的时候规范一些。

// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出A的异常
void* operator new (std::size_t size) throw (A);
// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();

单单靠上面3个,还是会出问题,比如写了抛abc,但实际上可能抛了d。
又比如函数深层调用,一层调一层那岂不是每一层函数都要写好多的throw()。
而且因为考虑到兼容c语言的问题,这只能是个建议而不是规定。


// C++11 中新增的noexcept,表示不会抛异常
thread() noexcept;
thread (thread&& x) noexcept;

//通过这个简化,只标记不会抛异常的函数。
但要小心的是,间接层加了noexcept,但这层函数调用的函数抛了异常,是不会检测到的。

而且一旦检测到抛出了规定外的异常,编译器也只是报错。

4.自定义异常体系

在实际过程中,大家都是设计一个异常体系,否则的下不同组各自管自己抛异常,最后害惨了外面管异常的,因为catch(...)是不确定究竟是什么异常,所以通过设计异常体系,让异常的信息更加的准确,方便处理。

 接下来是一个比较常见的异常体系

class Exception
{
public:
	Exception(const string& errmsg, int id)
		:_errmsg(errmsg)
		, _id(id)
	{}
	virtual string what() const
	{
		return _errmsg;   //返回错误描述  
	}
protected:
	string _errmsg;//错误描述,比如基础的错误信息,比如内存错误等等
	int _id; //错误id
};


//这里会有一个类,比如说是数据库开发组的。
class SqlException : public Exception
{
public:
	SqlException(const string& errmsg, int id, const string& sql)
		:Exception(errmsg, id)
		, _sql(sql)
	{}
	virtual string what() const
	{
		string str = "SqlException:";//表明自己是什么错误类的
		str += _errmsg;
		str += "->";
		str += _sql;
		return str;
        //再基础的错误信息基础上,加上自己的额外备注信息,让错误描述更加的准确
	}
private:
	const string _sql;
};

//缓存组,具体做什么因人而异,主要是不同的组都会加上一串字符串,表示这个异常是
//哪个组负责的
class CacheException : public Exception
{
public:
	CacheException(const string& errmsg, int id)
		:Exception(errmsg, id)
	{}
	virtual string what() const
	{
		string str = "CacheException:";
		str += _errmsg;
		return str;
	}
};

//网络组的,记录请求类型等等的
class HttpServerException : public Exception
{
public:
	HttpServerException(const string& errmsg, int id, const string& type)
		:Exception(errmsg, id)
		, _type(type)
	{}
	virtual string what() const
	{
		string str = "HttpServerException:";
		str += _type;
		str += ":";
		str += _errmsg;
		return str;
	}
private:
	const string _type;
};

那么如何捕获呢?

catch (const Exception& e) // 这里捕获父类对象就可以
 {
 // 多态
 cout << e.what() << endl;
 }

利用继承的特性,用父类对象的引用来接受派生类或者父类本身。

然后利用虚函数的特性,在满足多态的情况下,如果接受的派生类,那么what调用的就是派生类的what,如果接受的是父类,就会调用父类的what。

5.c++标准库的异常体系

 c++标准库也是弄了一个异常体系。

图片出自菜鸟编程。

6.异常优缺点

 1、优点

1、相比传统的错误码,异常可以更加清晰的展现出错误的信息,甚至包含堆栈的调用信息,这样可以帮助更好的定位bug

2、错误码必须通过层层的retrun返回错误码,然后才能拿到错误码,相比之下异常可以直接拿到信息,跳过中间的过程。

3、很多第三方的库都包含了异常,使用的时候也得关注异常。

4、部分情况用异常更好,比如构造函数没有返回值,只能抛异常。比如at这个函数,返回什么很重要,但问题是返回什么都可能不是错误信息,反而可能是一个正常的值,但确实下标越界了。

2、缺点

1、异常会导致程序的执行流乱跳,会非常混乱,尤其是运行时的时候抛异常就会乱跳。导致调试跟踪以及分析程序的时候会非常困难。

2、异常有一定的性能开销,不过在现在的硬件下,基本可以忽略不计。

3、c++没有垃圾回收机制,资源需要自己管理,容易造成内存泄漏、死锁等异常安全问题,想要解决,建议是rall(智能指针的内容)。

4、标准库的异常体系定义的一般,导致所有人都是各自定义各自的,比较混乱。

5、异常需要尽量的规范使用,否则负责捕获异常处理异常的人会很痛苦。主要是,所有的异常继承自一个基类;函数是否抛异常、抛什么异常,都使用func() throw()的形式规范

7、补充

异常一旦发生,说明程序出现了非法的情况
程序中只要有异常,就必须处理。一旦有抛出异常,那么必须捕获,否则代码最后会崩溃。

基类的const类型引用,可以捕获所有的子类的异常对象
 

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

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

相关文章

SAP-ABAP:使用ST05(SQL Trace)追踪结构字段来源的步骤

ST05 是 SAP 提供的 SQL 跟踪工具&#xff0c;可以记录程序运行期间所有数据库操作&#xff08;如 SELECT、UPDATE、INSERT&#xff09;。通过分析跟踪结果&#xff0c;可以精准定位程序中结构字段对应的数据库表。 步骤1&#xff1a;激活ST05跟踪 事务码 ST05 → 点击 Activa…

sklearn中的决策树-分类树:剪枝参数

剪枝参数 在不加限制的情况下&#xff0c;一棵决策树会生长到衡量不纯度的指标最优&#xff0c;或者没有更多的特征可用为止。这样的决策树 往往会过拟合。为了让决策树有更好的泛化性&#xff0c;我们要对决策树进行剪枝。剪枝策略对决策树的影响巨大&#xff0c;正确的剪枝策…

安宝特科技 | Vuzix Z100智能眼镜+AugmentOS:重新定义AI可穿戴设备的未来——从操作系统到硬件生态,如何掀起无感智能革命?

一、AugmentOS&#xff1a;AI可穿戴的“操作系统革命” 2025年2月3日&#xff0c;Vuzix与AI人机交互团队Mentra联合推出的AugmentOS&#xff0c;被业内视为智能眼镜领域的“iOS时刻”。这款全球首个专为智能眼镜设计的通用操作系统&#xff0c;通过三大突破重新定义了AI可穿戴…

基于Rook的Ceph云原生存储部署与实践指南(上)

#作者&#xff1a;任少近 文章目录 1 Ceph环境准备2 rook部署ceph群集2.1 Rook 帮助地址2.2 安装ceph2.3 获取csi镜像2.4 Master参加到osd2.5 设置默认存储 3 Rook部署云原生RBD块存储3.1 部署storageclass资源3.2 部署WordPress使用RBD3.3 WordPress访问 4 Rook部署云原生RGW…

2月27(信息差)

&#x1f30d;雷军超钟睒睒登顶中国首富 身家近4400亿元 &#x1f384;全球AI大混战升温&#xff01;超越Sora的阿里万相大模型开源 家用显卡都能跑 ✨小米15 Ultra、小米SU7 Ultra定档2月27日 雷军宣布&#xff1a;向超高端进发 1.刚刚&#xff01;DeepSeek硬核发布&#xff…

【Linux】文件系统深度解析:从基础到高级应用

&#x1f3ac; 个人主页&#xff1a;努力可抵万难 &#x1f4d6; 个人专栏&#xff1a;《C语法》《Linux系列》《数据结构及算法》 ⛰️ 路虽远&#xff0c;行则将至 目录 &#x1f4da;一、引言&#xff1a;文件系统的核心作用与历史演进 &#x1f4d6;1.文件系统的定义与功…

《Effective Objective-C》阅读笔记(中)

目录 接口与API设计 用前缀避免命名空间冲突 提供“全能初始化方法” 实现description方法 尽量使用不可变对象 使用清晰而协调的命名方式 方法命名 ​编辑类与协议命名 为私有方法名加前缀 理解OC错误模型 理解NSCopying协议 协议与分类 通过委托与数据源协议进行…

MongoDB—(一主、一从、一仲裁)副本集搭建

MongoDB集群介绍&#xff1a; MongoDB 副本集是由多个MongoDB实例组成的集群&#xff0c;其中包含一个主节点&#xff08;Primary&#xff09;和多个从节点&#xff08;Secondary&#xff09;&#xff0c;用于提供数据冗余和高可用性。以下是搭建 MongoDB 副本集的详细步骤&am…

【实战 ES】实战 Elasticsearch:快速上手与深度实践-1.3.1单节点安装(Docker与手动部署)

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 10分钟快速部署Elasticsearch单节点环境1. 系统环境要求1.1 硬件配置推荐1.2 软件依赖 2. Docker部署方案2.1 部署流程2.2 参数说明2.3 性能优化建议 3. 手动部署方案3.1 安…

Rt-thread源码剖析(1)——内核对象

前言 该系列基于rtthread-nano的内核源码&#xff0c;来研究RTOS的底层逻辑&#xff0c;本文介绍RTT的内核对象&#xff0c;对于其他RTOS来说也可供参考&#xff0c;万变不离其宗&#xff0c;大家都是互相借鉴&#xff0c;实现不会差太多。 内核对象容器 首先要明确的一点是什…

html css js网页制作成品——HTML+CSS甜品店网页设计(5页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…

Trae根据原型设计稿生成微信小程序密码输入框的踩坑记录

一、需求描述 最近经常使用Trae生成一些小组件和功能代码&#xff08;对Trae赶兴趣的可以看之前的文章《TraeAi上手体验》&#xff09;&#xff0c;刚好在用uniapp开发微信小程序时需要开发一个输入密码的弹框组件&#xff0c;于是想用Trae来实现。原型设计稿如下&#xff1a;…

斩波放大器

目录 简介 自稳零斩波放大器 噪声 简介 双极性放大器的失调电压为25 μV&#xff0c;漂移为0.1 μV/C。斩波放大器尽管存在一些不利影 响&#xff0c;但可提供低于5 μV的失调电压&#xff0c;而且不会出现明显的失调漂移&#xff0c; 以下图1给出了基本的斩波放大器电路图。…

windows设置暂停更新时长

windows设置暂停更新时长 win11与win10修改注册表操作一致 &#xff0c;系统界面不同 1.打开注册表 2.在以下路径 \HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings 右键新建 DWORD 32位值&#xff0c;名称为FlightSettingsMaxPauseDays 根据需求填写数…

DIALOGPT:大规模生成式预训练用于对话响应生成

摘要 我们提出了一个大规模、可调节的神经对话响应生成模型&#xff0c;DIALOGPT&#xff08;对话生成预训练变换器&#xff09;。该模型训练于从2005年至2017年间Reddit评论链中提取的1.47亿次类似对话的交流&#xff0c;DIALOGPT扩展了Hugging Face的PyTorch变换器&#xff…

Mac端不显示正常用户名,变成192的解决方法

今天打开终端&#xff0c;本应该显示机器名的&#xff0c;但是此时显示了192。 问题原因&#xff1a; 当路由器的DNS使用默认的 192.168.1.1 或 192.168.0.1 的时候 Terminal 里的计算机名 会变成 localhost。当路由器的DNS使用自定义的 例如 运营商的DNS 或者 公共DNS的时候 …

SD 卡无屏安装启动树莓派5

最近想用一下树莓派5&#xff0c;拿出来一看&#xff0c;是 Micro-HMDI 的接口&#xff0c;手头正好没有这个接口线&#xff0c;便研究如何在没有显示屏的情况下&#xff0c;安装启动树莓派。 一、使用 Raspberry Pi Imager 烧录 SD 卡 选择 Raspberry Pi Imager 来烧录 SD 卡…

Xlua 编译 Windows、UWP、Android、iOS 平台支持库

Xlua 编译 Windows、UWP、Android、iOS 平台支持库 Windows&#xff1a; 安装 Visual Studio&#xff08;推荐 2017 或更高版本&#xff09; 安装 CMake&#xff08;https://cmake.org/&#xff09; macOS&#xff1a; 安装 Xcode 和命令行工具 安装 CMake 检查 cmake 是否安…

npm : 无法加载文件 E:\ProgramFiles\Nodejs\npm.ps1,因为在此系统上禁止运行脚本。

这个错误是因为 Windows 系统的 PowerShell 执行策略 限制了脚本的运行。默认情况下&#xff0c;PowerShell 的执行策略是 Restricted&#xff0c;即禁止运行任何脚本。以下是解决该问题的步骤&#xff1a; 1. 检查当前执行策略 打开 PowerShell&#xff08;管理员权限&#x…

基于专利合作地址匹配的数据构建区域协同矩阵

文章目录 地区地址提取完成的处理代码 在专利合作申请表中&#xff0c;有多家公司合作申请。在专利权人地址中&#xff0c; 有多个公司的地址信息。故想利用这里多个地址。想用这里的地址来代表区域之间的专利合作情况代表区域之间的协同、协作情况。 下图是专利合作表的一部分…