C++异常及异常优缺点

news2024/11/24 17:09:16

🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
在这里插入图片描述

文章目录

  • 一、C语言传统的处理错误的方式
  • 二、C++异常
    • 1.throw catch
    • 2.异常的抛出和捕获
    • 3.异常的抛出和捕获原则
    • 4.函数调用链 异常 栈展开匹配原则
    • 5.异常的重新抛出
  • 三、异常安全问题
  • 四、异常规范
  • 五、异常优缺点

一、C语言传统的处理错误的方式

1.终止程序
比如assert去断言(assert只在Debug模式生效)
缺陷:用户难以接受,比如发生内存错误,除零错误时就会终止程序
2.返回错误码
缺陷:需要程序员自己去查对应的错误信息,比如系统的很多库接口函数都是把错误码放在error中,表示错误。

二、C++异常

异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或者间接的调用者去处理这个错误(调用链)

1.throw catch

**throw:**当问题出现时,程序会抛出一个异常,就是通过throw关键字来完成的
**catch:**在想处理问题的地方,通过异常处理程序捕获异常。catch关键字用于捕获异常,可以有多个catch进行捕获。

如果有一个块抛出一个异常,捕获异常的方法会使用try个catch关键字,try块中放置可能抛出异常的代码,try块中的代码被称为保护代码,使用try/catch语句的语法如下

try
{
    //保护代码
}catch(ExceptionName e1)
{
    //catch块
}catch(ExceptionName e2)
{
    //catch块
}catch(ExceptionName en)
{
    //catch块
}

面向对象的语言就讲究抛出一个对象,这个对象就可以抛出很多信息,当然也可以抛出一个整型,把错误码抛出来,但是一般不会这么做

2.异常的抛出和捕获

double Divison (int a,int b)
{
	if(b==0)
		throw "Division by zero condition!"
	else
		return ((double)a/(double)b);
}
void Func()
{
	int len,time;
	cin>>len>>time;
	cout<<Divison(len,time)<<endl;
}
int main()
{	
	try
	{
    //保护代码
	    Func();
	}catch(const char* errmsg)
	{
    //catch块
    	cout<<errmsg<<endl;
	}
}

结果:
如果没发生除零错误,则正常运行,会跳过catch块
如果发生除零错误,出现异常,则会直接沿着调用链找到最近的而且匹配的catch块,其他的会被跳过,比如上面的cout<<Division(len,time)<<endl;而且Divison和Func的函数栈帧会被销毁

3.异常的抛出和捕获原则

1.异常是通过抛出对象而引发的,该对象的类型决定应该了应该激活哪个catch的处理代码。
2.被选中的被选中的处理代码是调用链中与该对象类型匹配且离抛出对象最近的那一个,当走完了都还没有匹配的就会直接报错,终止程序是最严重最严重的情况了,所以一般情况下必须捕获异常。
为了提高系统的健壮性,会在某个合适位置放置一个…,这个catch块能捕获任何类型的异常,也可以放置出现未知的异常,程序终止

	try
	{
	    Func();
	}catch(const char* errmsg)
	{
 
	}catch(...)//捕获任意类型,所以一般放在函数调用链的最后
	{
	}
	

3.这个抛出的对象e和接收的对象e不是同一个,因为抛出的通常是在那个栈帧创建出的临时变量,当这个栈帧释放后就没了,所以是传值返回,会把拷贝对象给给catch的e,所以这里也会用到之前我们说的移动构造,将这个右值(将亡值)转移给我们

4.抛出和捕获异常的捕获原则有个例外,并不是要类型完全匹配,可以抛出派生类对象,使用基类捕获(赋值兼容转换)

4.函数调用链 异常 栈展开匹配原则

1.首先检查throw本身是否在try块内部,如果是就查找它匹配的catch语句,如果有匹配的,则调用catch的地方进行处理。
2.没有匹配的catch则退出当前的栈帧,依旧没有匹配的,继续在函数的栈帧中进行查找catch
3.如果到达main函数的栈,依旧没有匹配的,则终止进程(一般都会增加…,所以不会终止),上述这个沿着调用链查找匹配的catch子句的过程就被称为栈展开
4.找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。

5.异常的重新抛出

出现了某些异常,可能还想重新试几次,比如出现网络错误,必须要重试几次才行,不然直接报错了用户很难受

void SendMsg(string str)
{
	srand(time(0));
	//要求出现网络错误要重试三次
	if(rand()%3==0)
	{
		throw HttpServerException("请求资源不存在",100,"get");//自定义异常类,细节不展示
	}
	else if(rand()%4==0)
	{
		throw HttpServerException("权限不足",101,"post");
	}
	cout<<"发送成功:"<<str<<endl;
}
void HttpServer()
{
	string str ="zhupi";
	int n=3;
	while(n--)
	{
		try
		{
			SendMsg(str);
			break;//不抛异常就结束
		}
		catch(const Exception&e)//自定义异常基类,不展示细节
		{
			if(e.getid()=100&&n>0)
			{
				continue;
			}
			else
			{
				throw e;//异常的重新抛出
			}
		}
	}
}

1、当遇到网络层错误就多次加载,遇到其他错误就重新抛出(异常被捕获后进行部分处理,不能处理的重新抛出交给上一层 调用链异常栈展开)
2、这里的SendMsg必须放在try块中,如果实在while中进行SendMsg,那抛异常的话就会直接跳出该函数栈帧了,因为在try中才能捕获到

三、异常安全问题

1.构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全不初始化。
2.析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏,句柄未关闭等(文件流))
3.C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上问题(智能指针的核心原理)

比如说

void Func
{
	int *array1 = new int[10];
	int *array2 = new int[10];
	......
	try
	{
	}catch(...)
	{
		delete[] array1;
		delete[] array2;
		throw;//捕获什么重新抛出什么
	}
	delete[] array1;
	delete[] array2;
}

这样写代码有两个问题
1、代码非常丑陋,这样看都还好,那多几个new呢?
2、有可能会发生new抛异常的问题,也会造成内存泄漏的问题,防不胜防,这只有通过RAII的思想来完成,之后会用智能指针来说这个问题

四、异常规范

1.异常规范说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。
可以在函数后面接上 throw(类型1,类型2,…),列出这个函数可能抛掷的所有异常
2.函数后面接上throw()就代表不抛出异常
3.若无异常接口声明,则次函数可以抛掷任何类型的异常

//C++98
void fun() throw(A,B,C,D);
//这里表示这个函数会抛掷A/B/C/D中的某种类型的异常

void*operator new(size_t size) throw (bad_alloc);
//表示只会抛出bad_alloc的异常

void*operator delete(size_t size,void*ptr) throw();
//表示该函数不会抛出异常

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

C++98太理想了,比如这里声明throw(),但是有人在里面抛异常,再比如throw(A,B,C,D),有人在里面抛E,所以C++11就只用一个新的关键字noexcept来表示有还是没有异常,编译器不能强制规定,因为需要兼容C

五、异常优缺点

C++异常的优点:
1.异常对象的抛出,相比于错误码的方式可以清晰准确的展示出错的各种信息,甚至还可以包含堆栈的信息,这样可以很好的定位程序的Bug
2.返回错误码的传统方式有个问题就是:在函数调用链中,深层的函数返回了错误,我们我们需要层层返回,最外层才拿得到。而异常在调用链很深的时候,必须希望出错之后直接返回到main,那以前只有C的goto语句能够做到,现在的异常也能够做到。(中间跳过的栈帧是会被正常销毁的)
3.很多的第三方库都包含异常,我们使用的时候也需要使用异常
4.部分函数使用异常会更好处理,比如构造函数没有返回值,不方便使用错误码方式处理,再比如T&operator[](size_t)这样的函数,如果pos越界了只能通过终止程序或者使用异常来处理,没办法通过返回值表示错误。

C++异常的缺点:
1.异常会导致程序执行流乱跳的问题,而且非常混乱,并且是运行时出错抛异常就会乱跳,这导致我们跟踪调试时以及分析程序时,造成很大困难,比如在throw这条语句的后面位置打一个断点,抛异常的话这个程序是停不下来的。
2.异常会有一些性能的开销,现代硬件速度很快的情况下,这个基本忽略不计
3.C++没有垃圾回收机制,资源需要自己管理,有了异常就非常容易导致内存泄漏,死锁等异常安全问题,需要RAII思想来处理,学习成本高
4.C++标准库的异常体育定义得很不好,导致都定义自己的异常体系,非常混乱
5.异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户很难受

异常的致命缺点就是执行流乱跳
在这里插入图片描述

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

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

相关文章

微信小程序怎样开发?【小程序开发】

说到微信小程序&#xff0c;很多公司企业商家都会有自己的微信小程序&#xff0c;已经是他们的标配了。那么还没有自己的微信小程序的小伙伴&#xff0c;也在筹备着开发小程序。那么微信小程序怎样开发的呢&#xff0c;今天就教大家一个比较简单的开发方法。 微信小程序怎样开…

Linux进程通信之共享内存

一、共享内存之原理 1.是在物理内存中开辟了一片空间&#xff1b; 2.不同的进程通过页表将物理内存空间映射到自己的进程虚拟地址空间之中 3.不同的进程可以通过操作自己的虚拟地址空间中的虚拟地址去操作共享内存&#xff08;物理地址&#xff09; 共享内存是最快的进程之间…

为什么计算机中的负数要用补码表示?

本文已收录到 AndroidFamily&#xff0c;技术和职场问题&#xff0c;请关注公众号 [彭旭锐] 提问。 前言 大家好&#xff0c;我是小彭。 在前面的文章里&#xff0c;我们聊到了计算机的冯诺依曼架构的 3 个基本原则。其中第 1 个原则是计算机中所有信息都是采用二进制格式的编…

【在Spring MVC框架中,关于限制请求方式】

目录 1.关于限制请求方式 2. 附&#xff1a;关于GET和POST请求方式 1.关于限制请求方式 在Spring MVC框架中&#xff0c;RequestMapping注解的主要作用是配置请求路径&#xff0c;除此以外&#xff0c;还可以配置请求方式&#xff0c;例如&#xff1a; RequestMapping(value…

【Linux常见指令1】

目录&#xff1a;前言常用指令ls指令whoami && pwdcdtouch &#xff08;触摸&#xff09;mkdir &#xff08;make directory&#xff09;rmdir && rm (remove)mv(move 移动)cp(copy 拷贝)stat &#xff08;统计&#xff09;nanoechogccman&#xff08;重要&…

如何在一台服务器同一个端口运行多个pgbouncer

PGbouncer是Postgresql数据库最常用的一款连接池软件&#xff0c;但是它是单进程的&#xff0c;所以只能占用一颗CPU资源&#xff0c;会造成CPU资源的浪费。PGbouncer有方法在同一台服务器的同一个端口运行多个进程实例&#xff0c;可以让资源得到充分利用。 先看下一个pgbounc…

【愚公系列】2022年12月 使用win11系统自带SSH,远程控制VMware中Liunx虚拟机系统

文章目录前言1.cpolar简介2.cpolar功能一、使用win11系统自带SSH&#xff0c;远程控制VMware中Liunx虚拟机系统1.注册cpolar账号2.下载最新版Ubuntu系统3.Ubuntu系统安装curl4.Ubuntu系统安装cpolar5.Ubuntu开启SSH6.WIN11测试SSH总结前言 身为开发人员&#xff0c;虚拟化系统…

Java基础之《netty(6)—NIO快速入门》

一、案例 1、编写一个NIO入门案例&#xff0c;实现服务器端和客户端之间的数据简单通讯&#xff08;非阻塞&#xff09; 2、目的&#xff1a;理解NIO非阻塞网络编程机制 3、代码 NIOServer.java package netty.niostart;import java.io.IOException; import java.net.InetSoc…

死锁问题【javaEE初阶】

什么是死锁&#xff1f; 所谓死锁&#xff0c;是指多个进程在运行过程中因争夺资源而造成的一种僵局&#xff0c;当进程处于这种僵持状态时&#xff0c;若无外力作用&#xff0c;它们都将无法再向前推进。 因此我们举个例子来描述&#xff0c;如果此时有一个线程A&…

【pen200-lab】10.11.1.217

pen200-lab 学习笔记 【pen200-lab】10.11.1.217 &#x1f525;系列专栏&#xff1a;pen200-lab &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月30日&#x1f334; &#x1f36d;作…

node.js的认识与安装

一、node.js的认识 &#x1f4d6; 简单的说 Node.js 就是运行在服务端的 JavaScript。 Node.js 是一个基于 Chrome JavaScript 运行时建立的一个开源的、跨平台的JavaScript 运行时环境。 Node.js 是一个事件驱动 I/O 服务端 JavaScript 环境&#xff0c;基于 Google 的 V8 引…

ZMQ之脱机可靠性--巨人模式

当你意识到管家模式是一种非常可靠的消息代理时&#xff0c;你可能会想要使用磁盘做一下消息中转&#xff0c;从而进一步提升可靠性。这种方式虽然在很多企业级消息系统中应用&#xff0c;但我还是有些反对的&#xff0c;原因有&#xff1a; 1、我们可以看到&#xff0c;懒惰海…

【JS】数据结构之栈

文章目录基本介绍代码实现基本介绍 内存中的堆栈和数据机构中的堆栈不是一个概念&#xff0c;内存中的堆栈是真实存在的物理区&#xff0c;数据结构中的堆栈是抽象数据存储结构。 栈&#xff1a;是一种受限制的线性表。他遵循后进先出的原则&#xff08;LIFO&#xff09;其限制…

神仙级编程神器,吹爆

Visual Studio 编程领域公认的“最强IDE”&#xff0c;Visual Studio是目前最流行的Windows平台应用程序的集成开发环境&#xff0c;提供了高级开发工具、调试功能、数据库功能和创新功能&#xff0c;帮助在各种平台上快速创建当前最先进的应用程序&#xff0c;开发新的程序。 …

【ODX介绍】-5-用于Flash刷写的ODX-F文件概述

总目录:(单击下方链接皆可跳转至专栏总目录) 《UDS/OBD诊断需求编辑工具》总目录https://blog.csdn.net/qfmzhu/article/details/123697014 共9页精讲:在第二章节中,附上了一个完整的,且详细的ODX-F文件层级结构图。 目录 1 什么是ODX-F?

【在Spring MVC框架和Spring Boot项目中,控制器的响应结果】

目录 1. 控制器的响应结果 2. 相关配置 3. 使用枚举优化代码 1. 控制器的响应结果 当控制器处理了请求之后&#xff0c;向客户端响应的结果中&#xff0c;应该至少包含&#xff1a; 业务状态码&#xff1a;通常是数值类型的&#xff0c;客户端可以根据此数值来判断操作成功…

docke部署nodejs程序及Dockerfile详解

目录参考一、Dockerfile二、部署1、程序结构2、新建Dockerfile3、新建.dockerignore4、构建镜像5、创建容器6、关闭镜像参考 重点参考&#xff1a;把一个 Node.js web 应用程序给 Docker 化 Docker部署Node.js的方法步骤&#xff08;nodejs docker部署&#xff09; 一、Docke…

Linux服务器启动tomcat的三种方式

直接进入主题&#xff0c;首先cd进入tomcat的bin文件夹下&#xff0c;然后可以尝试以下三种启动方式&#xff1a; 第一种&#xff08;当前会话启动&#xff09;&#xff1a; ./startup.sh 效果: 然后tomcat就在后台启动了&#xff0c;我们还可以在当前会话中继续输入其它指令…

PHP基于thinkphp的网上书店管理系统#毕业设计

本论文主要论述了如何使用php语言开发一个网上图书管理系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;将论述网上图书管理系统的当前背景以及系统开发的目的&#xff0c;后续章节将严格按照软…

【python】 16进制字符串转list

def splitStringToByteList(bytesString): # 拆分字符串成字节列表bytesList []for i in range(int(len(bytesString)/2)):bytesList.append(bytesString[i*2:i*22])return bytesListif __name__ __main__:print(splitStringToByteList("1E1E2AEB4ACC4C")) 结果&…