【C++】C++异常语法、使用、规范、异常安全及异常的优缺点

news2024/11/25 6:34:41

1. C++异常概念

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

  • throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  • catch: 在您想要处理问题的地方,通过异常处理程序捕获异常.catch 关键字用于捕获异常,可以有多个catch进行捕获。
  • try: try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块。

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

try
{
	//可能会出现异常的代码块
	//即保护的标识代码
}
catch(ExceptionName e1)
{
	//catch快
}
catch(ExceptionName e2)
{
	//catch快
}
catch(ExceptionName eN)
{
	//catch快
}

2.异常的抛出和匹配原则

  1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
  2. 选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
  3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成
    一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)
  4. catch(…)可以捕获任意类型的异常,问题是不知道异常错误是什么。
  5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获,这个在实际中非常实用,我们后面会详细讲解这个
  6. 对于以上特性,为加深对其理解,代码解析如下:
double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
		//抛出char*类型字符串类型,则必须匹配对应
		//的类型来catch
		//抛出的异常会生成一个临时对象的拷贝
		//因为本层函数栈帧马上就要结束了!!!
		throw "Division by zero condition!";
	else
		return ((double)a / (double)b);
}
void Func1()
{
	try
	{
		int len, time;
		cin >> len >> time;
		cout << Division(len, time) << endl;
	}
	//发生异常,如果能与本层的catch匹配,则在本层函数栈帧抛异常
	//若果不能与本层函数栈帧的catch匹配,则结束本层函数栈帧,
	//去上一层函数栈帧继续匹配,直到main函数,
	//如果还不能匹配异常,则直接终止程序
	catch (int errid)
	{
		cout << errid << endl;
	}
}
int main()
{
	try 
	{
		Func1();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (int errid) 
	{
		cout << errid << endl;
	}
	catch (...)
	{
		cout << "unkown exception" << endl;
	}

	return 0;
}

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

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

4.异常的重新抛出

有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理.如下代码示例所示:

double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Division by zero condition!";
	}
	return (double)a / (double)b;
}
void Func()
{
	// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
	// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
	// 重新抛出去。
	int* array = new int[10];
	try {
		int len, time;
		cin >> len >> time;
		cout << Division(len, time) << endl;
	}
	catch (...)
	{
		cout << "delete []" << array << endl;
		delete[] array;
		throw;
	}
	// ...
	cout << "delete []" << array << endl;
	delete[] array;
}
int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	return 0;
}

5.异常安全

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

6.异常规则

  1. 异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的后面接throw(类型),列出这个函数可能抛掷的所有异常类型。
  2. C++98中函数的后面接throw(),表示函数不抛异常。C++11中函数的后面接except(),表示函数不抛异常。
  3. 若无异常接口声明,则此函数可以抛掷任何类型的异常。
// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator new (std::size_t size, void* ptr) throw()/noexcept();

7.C++标准库中的异常体系

C++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:
在这里插入图片描述
说明: 实际中我们可以可以去继承exception类实现自己的异常类。但是实际中很多公司像上面一样自己定义一套异常继承体系。因为C++标准库设计的不够好用。如下代码示例:

int main()
{
 	try{
 		vector<int> v(10, 5);
 		// 这里如果系统内存不够也会抛异常
 		v.reserve(1000000000);
 		// 这里越界会抛异常
 		v.at(10) = 100; 
 	}
 	catch (const exception& e) // 这里捕获父类对象就可以
 	{
 		cout << e.what() << endl;
 	}
 	catch (...)
 	{
 		cout << "Unkown Exception" << endl;
 	}
 	return 0;
}

8.异常的优缺点(面试有面到!!!)

C++异常的优点:

  1. 异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的bug。
  2. 返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那么我们得层层返回错误,最外层才能拿到错误,具体看下面的详细解释。
// 1.下面这段伪代码我们可以看到ConnnectSql中出错了,
// 先返回给ServerStart,ServerStart再
//返回给main函数,main函数再针对问题处理具体的错误。
// 2.如果是异常体系,不管是ConnnectSql
// 还是ServerStart及调用函数出错,都不用检查,因为抛
//出的异常异常会直接跳到main函数中catch捕获的地方,main函数直接处理错误。
int ConnnectSql()
{
	// 用户名密码错误
	if (...)
		return 1;
	// 权限不足
	if (...)
		return 2;
}
int ServerStart() {
	if (int ret = ConnnectSql() < 0)
		return ret;
	int fd = socket()
		if(fd < 0return errno;
}
int main()
{
	if (ServerStart() < 0)
		...
		return 0;
}
  1. 很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库,那么我们使用它们也需要使用异常。
  2. 很多测试框架都使用异常,这样能更好的使用单元测试等进行白盒的测试。
  3. 部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如T&operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。

C++异常的缺点:

  1. 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会导致我们跟踪调试时以及分析程序时,比较困难。
  2. 异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计。
  3. C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。这个需要使用RAII来处理资源的管理问题。学习成本较高。
  4. C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱。
  5. 异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。所以异常规范有两点:一、抛出异常类型都继承自一个基类。二、函数是否抛异常、抛什么异常,都使用 func()throw();的方式规范化。
    总结: 异常总体而言,利大于弊,所以工程中我还是鼓励使用异常的。

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

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

相关文章

为什么说数字化转型能帮助企业降本增效?

引言 数字化转型是当今商业领域中的关键议题&#xff0c;它不仅是技术的应用&#xff0c;更是一种战略性的变革&#xff0c;对企业而言具有重要意义。在这个数字化时代&#xff0c;企业需要不断适应和采纳新技术&#xff0c;以获得竞争优势并提高效率。 数字化转型旨在将传统业…

成都工业学院Web技术基础(WEB)实验三:CSS字体等属性使用

写在前面 1、基于2022级计算机大类实验指导书 2、代码仅提供参考&#xff0c;前端变化比较大&#xff0c;按照要求&#xff0c;只能做到像&#xff0c;不能做到一模一样 3、图片和文字仅为示例&#xff0c;需要自行替换 4、如果代码不满足你的要求&#xff0c;请寻求其他的…

001两数之和

题意 给出一个数组和一个目标值&#xff0c;让你在数组中找出和为目标值的两个数&#xff0c;并且这两个数在数组中的下标&#xff08;索引&#xff09;不同。 示例 输入&#xff1a;nums[2,7,11,15],target9 输出&#xff1a;[0,1] 解释&#xff1a;因为nums[0]nums[1]9&#…

Draw.io or diagrams.net 使用方法

0 Preface/Foreword 在工作中&#xff0c;经常需要用到框图&#xff0c;流程图&#xff0c;时序图&#xff0c;等等&#xff0c;draw.io可以完成以上工作。 official website:draw.io 1 Usage 1.1 VS code插件 draw.io可以扩展到VS code工具中。

PHP基础 - 数组遍历与排序

介绍 在PHP中,数组遍历和排序是常见的操作,用于对数组中的元素进行访问和排序 数组遍历 1)数值数组的遍历 使用 foreach 循环遍历数组:foreach 循环是最常用的遍历数组的方法,它可以遍历索引数组和关联数组。例如:$fruits = array("apple", "banana&q…

浪涌保护器综合选型应用方案

浪涌保护器SPD是一种用于防止电气设备和电子信息系统受到雷电或其他过电压的影响的装置。浪涌保护器的选型是一个重要的工程问题&#xff0c;因为不同的应用场景和设备要求需要不同的浪涌保护器。本文将介绍浪涌保护器的行业分类选型方案&#xff0c;浪涌保护器选型的作用和意义…

智能优化算法应用:基于缎蓝园丁鸟算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于缎蓝园丁鸟算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于缎蓝园丁鸟算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.缎蓝园丁鸟算法4.实验参数设定5.算法…

Python Django Suit:构建现代化的Django后台管理

概要 Django Suit是一款为Django后台管理提供现代、优雅界面的第三方应用&#xff0c;它致力于提升Django开发者的管理体验。本文将深入介绍Django Suit的安装、配置和高级功能&#xff0c;提供详实的示例代码&#xff0c;帮助大家更好地使用和定制Django后台管理界面。 安装与…

科学小论文

赵州桥&#xff0c;是一座右拱桥&#xff0c;它座落于河北省石家庄市赵县城南液河之上。 赵州桥因赵县古称赵州而得名&#xff0c;当地人称之为大石桥&#xff0c;以区别于城西门外的永通桥&#xff0c;也称小石桥。 赵州桥始建于隋代&#xff0c;由匠师李春设计建造&#xff…

Java关键字 —— static 与 final 详细解释!一看就懂 有代码实例运行!

&#x1f9f8;欢迎来到dream_ready的博客&#xff0c;&#x1f4dc;相信您对博主首页也很感兴趣o (ˉ▽ˉ&#xff1b;) &#x1f4dc;Java关键字 —— super 详细解释&#xff01;一看就懂 有代码实例运行&#xff01; 目录 前言 static static修饰类中的成员变量 static修…

【SpringSecurity】-- 认证、授权

文章目录 SpringSecurity简介快速入门1.准备工作1.2引入SpringSecurity 认证1.登录校验流程2.原理2.1SpringSecurity完整流程2.2认证流程详解 3.解决问题3.1思路分析3.2准备工作3.3.实现3.3.1数据库校验用户3.3.2密码加密存储3.3.3登录接口3.3.4认证过滤器3.3.5退出登录 授权1.…

Mybatis之核心配置文件详解、默认类型别名、Mybatis获取参数值的两种方式

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

指针(笔记1)

一、内存和地址 内存及其管理方法: 1.计算机主存储器由一个一个存储单元组成&#xff0c;微型计算机以字节作为存储单元 2.内存区的每一个字节有一个唯一的编号&#xff0c;这就是“地址”&#xff0c;它相当于教学楼中的教室号 3.内存地址&#xff0c;是一个无符号整数(un…

Modelscope Agent初体验与思考

背景&#xff1a;LLM → \to → Agent ChatGPT为代表的大语言模型就不用过多的介绍了&#xff0c;ChatGPT很强大&#xff0c;但是也有做不到的东西。 例如&#xff1a; 实时查询问题&#xff1a;实时的天气&#xff0c;地理位置&#xff0c;最新新闻报道&#xff0c;现实世…

树链剖分(知识点整理)

思路来源 https://www.tuicool.com/articles/ee2QZf6 spoj375(树链剖分)-CSDN博客 概念 直接扒过来了&#xff0c;懒得写了…… 显然轻子树比重子树小&#xff0c;就少于父亲的一半&#xff0c; 然后性质2的证明就是基于此的…… 因为重链是间断的&#xff0c;所以两条重链…

从零开始搭建企业管理系统(四):集成 Knife4j

集成 Knife4j 前言Knife4j是什么集成 Knife4j引入 pom 依赖添加基础配置启动程序测试完善文档信息编写配置类修改 UserController修改 UserEntity修改 BaseEntity 文档效果图swagger 界面knife4j 界面 前言 前面一小节我们使用postman来进行接口的调试&#xff0c;如果接口一多…

服务器GPU占用,kill -9 PID 用不了,解决办法

PID&#xff08;progress ID 进程ID&#xff09; 上图为占用情况&#xff0c;使用下面的指令都不管用 kill -9 PID kill -15 PID # 加入sudo 还是不行 # 等等网上的 chatgpt 提供的其他办法&#xff0c;一圈试了下来还是不管用最后解决办法 首先用下面的指令查看进程的树结构…

【LeetCode刷题-树】-- 99.恢复二叉树

99.恢复二叉树 方法&#xff1a; 对二叉搜索树进行中序遍历得到值序列不满足的位置找到对应被错误交换的节点记为x和y交换x和y两个节点 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* Tre…

2024 年顶级的 Android 系统修复软件与方法

您是否正在寻找可以修复 PC 上 Android 操作系统的工具&#xff1f;这是我们精选的最好的 Android 系统修复软件&#xff01; Android 是世界著名的智能手机操作系统。全世界有数百万人使用这个操作系统&#xff0c;这使得它安全可靠。然而&#xff0c;这仍然不能使它完美无缺…

[足式机器人]Part2 Dr. CAN学习笔记-自动控制原理Ch1-2稳定性分析Stability

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-自动控制原理Ch1-2稳定性分析Stability 0. 序言1. 稳定的分类2. 稳定的对象3. 稳定的系统4. 系统稳定性的讨论5. 补充内容——Transfer Function(传递函数) - nonzero Initial Condition(非零初始…