【C++】异常

2023-11-16

C语言错误处理

在C语言中,因为没有异常这个机制,所以出现错误时一般都是以下方式解决

  • 终止程序:例如断言assert,当出现错误时,就会调用一个exit()或者abort()终止程序
    • 缺陷:出现错误直接终止,错误没有解决用户体验差
  • 错误码:例如大部分系统接口正确返回0错误返回1,也有一些是用全局变量errno来标记错误
    • 缺陷:需要自己查错误码定位问题,如果错误码过多,就会非常麻烦
  • 保存上下文数据:例如setjmp,longjmp。当出现问题时恢复上下文数据
    • 缺陷:规避了问题,但是问题还是没有得到处理,并且效率不高

所以C++引入了异常处理机制


异常的概念

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

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

可以抛出任意类型的对象。抛出的异常必须捕获。try要和catch匹配使用catch里的内容抛出异常时才执行,没有异常,不执行

#include<iostream>
using namespace std;

void fun() {
	int i, j;
	cin >> i >> j;
	if (j == 0) {
        //除0时抛出异常/可以是任意类型
		throw "除0";
	}
	cout << i / j;
}

int main() {
	try{
        // 保护的标识代码
		fun();
		char* p = new char[0x7fffffff];
	}
	catch (const std::exception& e){ //捕获new失败异常
		cout << e.what() << endl;
	}
     //捕获异常时要捕获抛出异常对应的类型
	catch (const char* s) { //捕获除0错误异常
		cout << s << endl;
	}
	return 0;
}

异常的使用

异常的抛出匹配原则

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

异常的栈展开匹配原则

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

在这里插入图片描述


异常安全

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

为了解决可能出现的资源泄漏问题,可以用异常的重新抛出执行资源的清理

异常的重新抛出

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

比如上面可能出现的异常安全问题,也就是在new之后抛出了异常,没有进行delete,那么就可以在抛出异常之前进行delete

void Func(){
	// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放
	// 所以这里捕获异常后并不处理异常,异常还是交给外面处理
	// 这里捕获了再重新抛出去
	int* array = new int[10];
	try {
		int a, b;
		cin >> a >> b;
		if (b == 0){
			throw "除零错误!";
		}

		int c = a / b;
	}
	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;
}

异常规范

  • 异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的后面接throw(类型),列出这个函数可能抛掷的所有异常类型
  • 函数的后面接throw(),表示函数不抛异常C++11可以在函数后面加noexcept
  • 无异常接口声明,则此函数可以抛掷任何类型的异常
// 这里表示这个函数会抛出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();
// C++11-不会抛出异常
void A() noexcept;

异常体系

C++标准库的异常体系

C++ 提供了一系列标准的异常,定义在<exception>中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结 构组织起来的,如下所示:

在这里插入图片描述

在这里插入图片描述

实际中我们可以可以去继承exception类实现自己的异常类。但是实际中很多公司像上面一样自己定义一套异常继承体系。因为C++标准库设计的不够好用


C++标准库的异常处理体系其实不是很完善,很多常见的错误都没有进行处理,并且在我们具体使用中,还可能会根据使用的情景、环境不同,出现更多样的异常,所以通常针对这种情况,我们会自定义一套自己的异常体系进行规范的异常管理。通过继承基类对象,来实现更多的异常。抛出时抛出一个派生类用基类来捕获即可

在这里插入图片描述

class Exception{
public:
	Exception(int errid, const char* errmsg)
		: _errid(errid)
		, _errmsg(errmsg)
	{}
	virtual string what() const{
		return _errmsg;
	}
protected:
	string _errmsg;
	int _errid;
};
class SqlException : public Exception{
public:
	SqlException(int errid, const char* errmsg, const char* sql = "")
		:Exception(errid, errmsg)
		, _sql(sql)
	{}
	virtual string what() const{
		string msg = "SqlException:";
		msg += _errmsg;
		msg += " sql:";
		msg += _sql;
		return msg;
	}
private:
	string _sql;
};
class CacheException : public Exception{
public:
	CacheException(int errid, const char* errmsg)
		:Exception(errid, errmsg)
	{}
	virtual string what() const{
		string msg = "CacheException:";
		msg += _errmsg;
		return msg;
	}
};
void f1(){
	// ....
	int i;
	cin >> i;
	if (i == 0)
		throw CacheException(1, "数据不存在");
}
void f2(){
	int i;
	cin >> i;
	if (i == 0)
		throw SqlException(1, "数据库查询失败", "select * from t_student");
}
int main(){
	try {
		f1();
		f2();
	}
	catch (const Exception& e){ // 这里直接捕获父类对象的引用就可以
		cout << e.what() << endl;
	}
	catch (...){
		cout << "未知异常" << endl;
	}
	return 0;
}

异常的优缺点

异常的优点

  • 异常对象定义好了,相比较于错误码,可以清晰准确的展示出错误的各种信息,甚至包含堆栈调用信息,可以帮我们很好的定位程序的bug
  • 在函数调用链中,深层函数返回错误,我们得层层返回,需要不断的判断是什么错误,再返回给最外层。异常直接会找到对应的catch执行不需要判断是什么错误
  • 很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库,那么我们使用它们也需要使用异常,可以更好地维护代码
  • 很多测试框架都使用异常,这样能更好的使用单元测试等进行白盒的测试
  • 部分函数使用异常更好处理,比如构造函数没有返回值不方便使用错误码方式处理:比如T&operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误

异常的缺点

  • 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会导致我们跟踪调试时以及分析程序时,比较困难
  • C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题,这个需要使用RAII来处理资源的管理问题,学习成本较高
  • C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱

异常总体而言,利大于弊,所以工程中我们还是鼓励使用异常的。另外OO的语言基本都是用异常处理错误,这也可以看出这是大势所趋

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C++】异常 的相关文章

  • 无法使用 strptime() 获取秒数

    我收到 YYYY MM DDThh mm ss S Z hh mm 这种格式的日期时间 我正在尝试使用复制该值strptime如下所示 struct tm time 0 char pEnd strptime datetime Y m dT
  • ROWNUM 的 OracleType 是什么

    我试图参数化所有现有的 sql 但以下代码给了我一个问题 command CommandText String Format SELECT FROM 0 WHERE ROWNUM lt maxRecords command CommandT
  • 在 Xamarin Android 中将图像从 URL 异步加载到 ImageView 中

    我有一个包含多个项目的 ListView 列表中的每个项目都应该有一个与之关联的图像 我创建了一个数组适配器来保存每个列表项并具有我希望加载的图像的 url 我正在尝试使用 Web 请求异步加载图像 并设置图像并在加载后在视图中更新它 但视
  • C++ 求二维数组每一行的最大值

    我已经设法用这个找到我的二维数组的每一行的最小值 void findLowest int A Cm int n int m int min A 0 0 for int i 0 i lt n i for int j 0 j lt m j if
  • FFMPEG Seeking 带来音频伪影

    我正在使用 ffmpeg 实现音频解码器 在读取音频甚至搜索已经可以工作时 我无法找到一种在搜索后清除缓冲区的方法 因此当应用程序在搜索后立即开始读取音频时 我没有任何工件 avcodec flush buffers似乎对内部缓冲区没有任何
  • fgets() 和 Ctrl+D,三次才能结束?

    I don t understand why I need press Ctrl D for three times to send the EOF In addition if I press Enter then it only too
  • 使用 Microsoft Graph API 订阅 Outlook 推送通知时出现 400 错误请求错误

    我正在尝试使用 Microsoft Graph API 创建订阅以通过推送通知获取 Outlook 电子邮件 mentions 我在用本文档 https learn microsoft com en us graph api subscri
  • 使用 C# 在 WinRT 中获取可用磁盘空间

    DllImport kernel32 dll SetLastError true static extern bool GetDiskFreeSpaceEx string lpDirectoryName out ulong lpFreeBy
  • 使用 Google Analytics API 在 C# 中显示信息

    我一整天都在寻找一个好的解决方案 但谷歌发展得太快了 我找不到有效的解决方案 我想做的是 我有一个 Web 应用程序 它有一个管理部分 用户需要登录才能查看信息 在本节中 我想显示来自 GA 的一些数据 例如某些特定网址的综合浏览量 因为我
  • c 中的错误:声明隐藏了全局范围内的变量

    当我尝试编译以下代码时 我收到此错误消息 错误 声明隐藏了全局范围内的变量 无效迭代器 节点 根 我不明白我到底在哪里隐藏或隐藏了之前声明的全局变量 我怎样才能解决这个问题 typedef node typedef struct node
  • HttpClient 像浏览器一样请求

    当我通过 HttpClient 类调用网站 www livescore com 时 我总是收到错误 500 可能服务器阻止了来自 HttpClient 的请求 1 还有其他方法可以从网页获取html吗 2 如何设置标题来获取html内容 当
  • A* 之间的差异 pA = 新 A;和 A* pA = 新 A();

    在 C 中 以下两个动态对象创建之间的确切区别是什么 A pA new A A pA new A 我做了一些测试 但似乎在这两种情况下 都调用了默认构造函数 并且仅调用了它 我正在寻找性能方面的任何差异 Thanks If A是 POD 类
  • 使用向量的 merge_sort 在少于 9 个输入的情况下效果很好

    不知何故 我使用向量实现了合并排序 问题是 它可以在少于 9 个输入的情况下正常工作 但在有 9 个或更多输入的情况下 它会执行一些我不明白的操作 如下所示 Input 5 4 3 2 1 6 5 4 3 2 1 9 8 7 6 5 4 3
  • 编译的表达式树会泄漏吗?

    根据我的理解 JIT 代码在程序运行时永远不会从内存中释放 这是否意味着重复调用 Compile 表达式树上会泄漏内存吗 这意味着仅在静态构造函数中编译表达式树或以其他方式缓存它们 这可能不那么简单 正确的 他们可能是GCed Lambda
  • 初始化变量的不同方式

    在 C 中初始化变量有多种方法 int z 3 与 int 相同z 3 Is int z z 3 same as int z z 3 您可以使用 int z z 3 Or just int z 3 Or int z 3 Or int z i
  • C 中的位移位

    如果与有符号整数对应的位模式右移 则 1 vacant bit will be filled by the sign bit 2 vacant bit will be filled by 0 3 The outcome is impleme
  • 将应用程序从 Microsoft Access 迁移到 VB 或 C#.NET

    我目前正试图说服管理层需要将我们的应用程序之一移植到 NET 该应用程序已经发展成为 Access 中的一个庞然大物 SQL 后端 拥有 700 个链接表 650 个表单 子表单 130 个模块和 850 个查询 我几乎知道这样做的所有主要
  • ListDictionary 类是否有通用替代方案?

    我正在查看一些示例代码 其中他们使用了ListDictionary对象来存储少量数据 大约 5 10 个对象左右 但这个数字可能会随着时间的推移而改变 我使用此类的唯一问题是 与我所做的其他所有事情不同 它不是通用的 这意味着 如果我在这里
  • 在 ASP.NET 中将事件冒泡为父级

    我已经说过 ASP NET 中的层次结构 page user control 1 user control 2 control 3 我想要做的是 当控件 3 它可以是任何类型的控件 我一般都想这样做 让用户用它做一些触发回发的事情时 它会向
  • Bing 地图运行时错误 Windows 8.1

    当我运行带有 Bing Map 集成的 Windows 8 1 应用程序时 出现以下错误 Windows UI Xaml Markup XamlParseException 类型的异常 发生在 DistanceApp exe 中 但未在用户

随机推荐

  • oracle 的 start with connect by 用法 .

    分类 oracle java 2012 11 27 17 38 489人阅读 评论 0 收藏 举报 目录 ORACLE Connect ByLevelStart With的使用Hierarchical query 层次查询 connect
  • 一张图解释什么是遗传算法_遗传算法总结(#看了就能懂和用系列#)

    Word害我重写 顺便重新整理下思路 背景 写论文时用到遗传算法 花了近一周时间 还算理解了算法以及能够进行基础的编程实现 保持谦虚 说明 具体的实现没敢细讲 主要是原理的方法上的介绍 讲解都算不上 先说说算法学习 个人觉得首先需要了解这个
  • SpringBoot--Eureka

    SpringBoot Eureka 项目一 使用Eureka注册服务 任务一 搭建Maven父工程 任务二 搭建服务端工程 任务三 搭建客户端工程 项目二 实现服务间的调用 任务一 搭建订单服务工程 任务二 编写用户服务功能 任务三 启动服
  • buuctf-loveSQL

    进入界面 没有什么明显的提示 上次有个一样界面的题目 结果直接万能密码1 1出来了 这次再试试 啥 简单的我有点不相信 睿智的我一下子就觉得不对经 去尝试一下MD5 就知道 算了 接着找吧 看题目名字 lovesql 估计还是注入 只能把目
  • lol 那个服务器最稳定,lol哪个区的人多,哪个区的技术最好?

    每日科技网 一直都有 一区的螃蟹能走位单杀打野 郊区王者打不过一区钻石 的搞笑言论 其实这个言论有一定的是事实依据 虽然有一点夸张 说的神乎其神的 电信一区艾欧尼亚在LOL中的地位是大家有目共睹的 而之所以会出现那样的畸形观点 主要呢有以下
  • js获取昨天/明天、本周/上周/下周、本月/上月、本季度/上季度、上一年的开始/结束日期

    一 获取昨天 明天的日期 该方法参数如果为true 则获取昨天日期 反之为明天日期 默认为true function getYestDayOrNextDay flag true 获取当前日期 const today new Date 计算前
  • 代码检视(一)

    一 意义 最大的意义 通过代码检视 发现问题 解决问题 并且能够有效地提升自己 最终达到写出优质代码 提高代码质量 成功做好项目 二 基本要求 一 逻辑一定要正确 逻辑错误非常容易引起BUG 二 避免一些没必要的代码 能用一句解决的就不要用
  • XMLSocket

    XMLSocket 协议是flash的长连接消息协议 XMLSocket协议规则 每个 XML 消息都是一个完整的XML文档 一定要以 0 结束 html5 websocket是长连接传输的是精简的http报文 XMLSocket conn
  • 【UE4】复杂背景人像抠图-飞浆AI-paddlepaddle深度训练模型

    前言 运用到Python3 7 UEC 蓝图 实现复杂背景人物使用PaddleHub深度训练模型进行抠像后在UE中使用 纯色背景人物仅材质就可实现 使用到的训练模型 deeplabv3p xception65 humanseg 1 准备工作
  • 【私有云平台的搭建——vSphere Client 的安装与配置】

    目录 vSphere Client 的基本操作 Step 1 使用 vSphere Client 安装虚拟机 Step 2 使用 vSphere 客户端在 ESXi 创建虚拟机 Step 3 点击 New Virtual Machine S
  • Idea启动报错idea start failed -org.picocontainer.PicoContainer com.intellij.openapi.application.Applicat

    org picocontainer PicoContainer com intellij openapi application Application getPicoContainer ror Please refer to https
  • Android编译详解之lunch命令

    Android的优势就在于其开源 手机和平板生产商可以根据自己的硬件进行个性定制自己的手机产品 如小米 LePhone M9等 因此 在我们在对Android的源码进行定制的时候 很有必要了解下 Android的编译过程 如果你从来没有做过
  • 技术人员的赚钱之道-2:做个现代的“六化”程序员

    六化 像是一面黑夜中的灯塔 在黑暗指明方向 六化 可是现代程序员具备的能力水平 六化 也可以是程序员轻创业的方式 什么是六化 专业化 数字化 自动化 虚拟化 云化 智能化 1 专业化 专业化是程序员的基础 懂得编程或某个专业领域的技术 2
  • Ubuntu20.04 LTS 安装GCC11.2教程,包教包会!

    GCC 11 2 安装 其他版本 如9 5 12 1等都可以用同样方法编译安装 但是依赖包不一样 需要到gcc官网下载对应的依赖包和源码包 前置条件 首先把Ubuntu提供的各种构建工具都给他装上 sudo apt install buil
  • 好分数阅卷3.0_揭秘!自考阅卷的批改套路!

    距离2019年4月自考仅剩 13 天 每当考试之后有小伙伴就有这样的感受 自己感觉这次可以 及格没问题 但是最后却是差了几分 也有人说 我都抄到了标准答案 为什么是56分 56分啊 难道自考真的有所谓的过关率 阅卷老师真的有刻意在压低分数
  • C++顺序表的构建(用数组存储数据)

    这是最简单的顺序表 顺序表中的元素都存储在数组T data中 const int defaultSize 100 template
  • 3399的-mipi适应多个lcd屏显示-后续2-linux内核中的修改

    一 前提 1 rk3399核心板 2 linux4 4 19 源码 3 多个MIPI显示屏的启动序列以及显示时序 重要 4 rk3399MIPI通道0 5 接上一个uboot中的修改配置 二 内核驱动的修改 0 dts就不再给出了 请参考u
  • 【PyQT5教程】-01入门PyQT5

    PyQT介绍 1 Qt 1 1 介绍 Qt 读作 cute 是一个跨平台的C 应用程序开发框架 最初由挪威公司Trolltech 现在是Qt公司的一部分 开发 Qt提供了一系列工具和类库 用于开发图形界面应用程序 命令行工具和服务器端应用程
  • k8s v1.16设置Job ttlSecondsAfterFinished不生效

    目录 Completed的job默认不会清理 配置自动清理job ttl机制自动清理完成的job ttl controller 开启 TTLAfterFinished kube apiserver开启TTLAfterFinished kub
  • 【C++】异常

    文章目录 C语言错误处理 异常的概念 异常的使用 异常的抛出匹配原则 异常的栈展开匹配原则 异常安全 异常的重新抛出 异常规范 异常体系 C 标准库的异常体系 异常的优缺点 C语言错误处理 在C语言中 因为没有异常这个机制 所以出现错误时一