【C++11】异常机制

文章目录

  • 一. 什么是异常?
  • 二. 为什么要引入抛异常机制?
    • 方法一:直接终止程序
    • 方法二:返回错误码
    • 方法三:C 标准库中的 setjmp 和 longjmp 组合
    • 总结 C 中处理异常的方式
  • 三. 如何进行抛异常?
    • 1. 关于抛出的异常对象的类型
    • 2. 抛出的异常对象一定要有对应类型的捕获
    • 3. 异常的执行流顺序
    • 4. 在函数调用链中异常栈展开匹配原则
    • 5. 规范的异常处理
  • 四. 异常的重新抛出
  • 五. 异常安全
  • 六. 异常规范
  • 七. 异常的优缺点
  • 八. C++ 标准库的异常体系

一. 什么是异常?

异常是一种处理错误的方式:当一个函数发现自己无法处理的错误时就会停止往下运行,然后把这个异常抛出去,让这个函数的直接或间接调用者去处理这个错误。

C++ 中异常相关的关键字有如下三个:

  • throw:当问题出现时,程序可以通过 throw 关键字来抛出一个异常(异常其实就是一个对象,可以是基本类型,也可以是自定义类型)
  • try:try 块中的代码标识“将被激活的特定异常”,即可能抛出异常的代码块必须在 try 内,它后面通常跟着一个或多个 catch 块。
  • catch:在您想要处理问题的地方,通过异常处理程序捕获异常,catch 关键字用于捕获异常,可以有多个 catch 进行捕获。

PS:如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码(try 块中的代码被称为保护代码),然后后面通常跟着一个或多个 catch 块用来捕获不同类型的异常对象(异常对象可以是基本类型,也可以是自定义类型,这个由自己来设定)。

使用 try/catch 语句的语法格式如下所示:

try
{// 可能抛出异常对象的代码
}
catch( A a ) //捕获 try 中抛出的类型为 A 的异常对象
{// 处理异常
}
catch( B b ) //捕获 try 中抛出的类型为 B 的异常对象
{// 处理异常
}
catch( C c ) //捕获 try 中抛出的类型为 C 的异常对象
{// 处理异常
}
...

二. 为什么要引入抛异常机制?

在 C 语言中,传统的处理错误的方式有如下三个:

方法一:直接终止程序

  • 如发生内存错误、除 0 错误时,操作系统会给进程发送 SIGSEGV 信号、SIGFPE 信号来直接终止进程。
  • 如 assert(…) 函数。缺陷:用户难以接受。
// 在 memcpy 函数的实现里,一开始就要进行断言检查
void* memcpy(void* dest, const void* src, unsigned int num)
{//断言,判断传入地址的有效性,防止野指针assert(dest!=NULL);assert(src!=NULL);//...
}

方法二:返回错误码

缺陷:需要程序员自己去查找对应的错误

比如系统的很多库的接口函数,调用失败时都是通过把错误码放到 errno 中:
在这里插入图片描述

又比如父进程等待子进程时,通过一个输出型参数来解析子进程的退出状态
在这里插入图片描述

方法三:C 标准库中的 setjmp 和 longjmp 组合

setjmp 和 longjmp是 C 标准库中的函数,它们用于实现非局部跳转(non-local jumps)。这意味着你可以在程序的不同位置之间跳转,而不仅仅是在函数之间跳转。

setjmp
函数原型如下:

int setjmp(jmp_buf env);

函数说明:setjmp 函数用于保存程序的当前执行环境,包括 CPU 寄存器和栈信息,并将这些信息保存在 jmp_buf 类型的变量 env 中。然后,setjmp 函数返回 0,表示保存环境成功。

longjmp
函数的原型如下:

void longjmp(jmp_buf env, int val);

函数说明:longjmp 函数用于从一个先前保存的环境(通过 setjmp 函数保存的)恢复程序的执行。它接受两个参数,第一个参数是保存的环境 env,第二个参数是一个整数值 val,它指定了程序应该跳转到setjmp调用的位置,并且可以传递一个值给 setjmp 函数。程序将继续执行,就好像从 setjmp 返回一样。

总结 C 中处理异常的方式

实际中 C 语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序的方式处理非常严重的错误。

每次出错要么直接崩溃,要么返回一个错误码(可读性差,还要自己对照错误码去找错误信息)。使用抛异常的话,当一个函数发现自己无法处理的错误时就可以抛出异常,让这个函数的直接或间接调用者去自定义处理这个错误的方式。

三. 如何进行抛异常?

1. 关于抛出的异常对象的类型

throw 抛出异常对象,异常对象可以是任意类型

异常是通过抛出对象而引发的,该对象的类型决定了应该激活对应 try 之下哪个 catch 的处理代码:
在这里插入图片描述

2. 抛出的异常对象一定要有对应类型的捕获

异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个 catch 的处理代码。如果没有捕获,或者没有匹配类型的捕获,则程序终止并报错:
在这里插入图片描述

补充: catch(…)可以捕获任意类型的异常,问题是捕捉到之后不知道这个异常错误是什么

如果有异常被 catch(…) 捕获到了,这个时候说明我们当前程序中还存在某种异常没有被去处理和考虑到

int Test()
{try{throw "exception";}catch(...){cout << "未知异常" << endl;}return 0;
}//------输出结果------
未知异常

3. 异常的执行流顺序

异常对象被抛出后,执行流会直接跳转到捕获它的地方,然后一直往下执行,不会再回去:
在这里插入图片描述

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

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

核心:被选中的 catch 处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。

示例:函数多层调用时,异常对象的被捕捉情况

在这里插入图片描述

PS:在实际中,一般都是把异常统一抛到最外层调用链(main 函数)去处理,然后最外层调用链拿到这些异常后,会写日志记录它们。

5. 规范的异常处理

在实际情况中,可能存在许多种不同类型的异常,我们可以包装一个类来统一描述异常对象(异常的错误码和错误描述),然后抛异常时,直接抛出这个类的对象即可:

#include <iostream>
using namespace std;// 自定义一个类,用来封装异常对象(包括异常信息和异常编号)
class MyException
{
public:// 构造函数传入异常编号和异常信息MyException(const int id, const string& msg):_errid(id), _errmsg(msg){}// 获取异常编号int GetErrId() const{return _errid;}// 获取异常信息string What() const{return _errmsg;}private:int _errid;    //异常编号string _errmsg;//异常信息
};int Division()
{int a, b;cin >> a >> b;if (b == 0) throw  MyException(1, "除法:除0错误");return a / b;
}int Remainder()
{int a, b;cin >> a >> b;if (b == 0) throw  MyException(1, "取模:除0错误");return a % b;
}int main()
{try{Division();Remainder();}catch (const MyException& e){cout << "错误码:" << e.GetErrId() << endl;cout << "错误信息:" << e.What() << endl;}return 0;
}

现实中,很多公司都会去自己定义公司专属的异常体系来让自己的异常管理规范化。如果一个项目中每个人都随意抛异常,那么外层的调用者基本就没办法玩了,他要对每种类型的异常对象去设定专门的 catch 捕捉处理。

更实用的是去定义一套继承的异常体系,这样大家抛出的都是继承的派生类对象,最外层调用者只需捕获一个基类对象就可以了,因为派生类对象可以切片赋值给基类对象:
在这里插入图片描述

总结:建立一个异常继承体系:抛出派生类对象,使用基类对象来捕获,这样只需写一个 catch 就能捕捉到多种类型的异常;此外我们还可以借助多态来增强异常体系的灵活性。

四. 异常的重新抛出

C++ 中异常经常会导致资源泄漏的问题:

  • 比如在 new 和 delete 中间抛出了异常,导致内存泄漏
  • 在 lock 和 unlock 中间抛出了异常导致死锁

下面演示在 new 和 delete 中间抛出异常,导致内存泄漏的场景:
在这里插入图片描述

解决办法:在自己调用链中先把这个异常对象给捕了,然后在 catch 中完成 delete 资源清理的工作之后,再把这个异常重新抛出到上面的调用链
在这里插入图片描述

PS:不过这种写法太挫了,不推荐这样处理。在 C++ 更推荐使用 RAII 来解决这种情况。

五. 异常安全

在使用异常机制时,应该考虑到以下几点:

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

六. 异常规范

C+11 定义异常规范的目的是为了让函数使用者知道该函数可能抛出的异常有哪些:

  1. 可以在函数的后面接 throw(类型),列出这个函数可能抛掷的所有异常的类型
  2. 函数的后面接 throw() 或者 noexcept,表示这个函数不抛异常
  3. 若无异常接口声明,则此函数内可以抛掷任何类型的异常
// 1、这里表示这个函数会抛出 A/B/C/D 中的某种类型的异常
void fun() throw(A,B,C,D);// 2、这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);// 3、这里表示这个函数不会抛出异常(下面两种写法等价)
// void* operator new (std::size_t size, void* ptr) noexcept;
void* operator new (std::size_t size, void* ptr) throw();

查阅 C++ 官方文档时可以看到有些函数标明了 noexcept,表示该函数不会抛出异常:

在这里插入图片描述

补充:函数的异常规范可以是函数更干净,但它并不是强制的,C++ 标准委员会也不敢给这种规范设计成强制的,因为之前的很多企业已经累计了很多的 C++ 代码,如果把这个规范设计成强制的话,之前的代码都会编译不通过。不过我们自己写代码还是遵守这个规范比较好。

七. 异常的优缺点

C++ 异常的优点

  • 异常对象定义好了,相比只返回一个错误码的方式,异常对象可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助我们更好的定位程序的 bug。
  • 返回错误码的传统方式有个很大的问题就是:在函数调用链中,深层函数返回了错误码之后,之前每个调用该函数的地方都需要检查返回值,那么我们得层层返回错误,直到最终最外层拿到错误码后才结束;而 C++ 中的异常让错误的处理简化了,出错的地方只管抛出异常即可,然后可以直接跳转到对这个异常对象的捕捉处。
  • 很多知名的第三方库都包含异常,比如 boost、gtest、gmock 等等常用的库,如果我们的项目不用的话,就不能很好的配合。
  • 很多测试框架都使用异常,这样能更好的使用单元测试等进行白盒的测试
  • 部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如 T& operator[ ] (size_t pos) 这样的函数,如果 pos 越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。

C++ 异常的缺点

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

总结:异常总体而言,利大于弊,所以工程中我们还是鼓励使用异常的。另外 OO 的语言基本都是用异常来处理错误的,异常一定是未来的大势所趋。

八. C++ 标准库的异常体系

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

在这里插入图片描述

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

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

相关文章

[QOpenGLWidget+QMouseEvent]实时绘制长方形

复现moho-打卡第1天 - 20240402 1.1--QOpenGLWidget中显示长方形 实现方法&#xff1a;顶点着色器中给定长方形的四个顶点数据&#xff0c;代码如下&#xff1a; // 顶点位置 GLfloat vertics[1][4][3] { {{mousePressPosX,mousePressPosY,0.0},{mousePressPosX,mouseMoveP…

网络爬虫:爬取假数据

网络爬虫&#xff1a;爬取假数据 文章目录 网络爬虫&#xff1a;爬取假数据前言一、项目介绍&#xff1a;二、项目来源&#xff1a;三、架构图&#xff1a;&#xff08;流程图&#xff09;四、使用了什么技术&#xff1a;&#xff08;知识点&#xff09;五、结果示意图&#xf…

Qt使用opencv,进行视频录制,功能打开、关闭摄像头,开始、结束录制视频,暂停、继续录制,并保存视频文件

1.效果图 2 代码实现 2.1 .h文件 #ifndef VIDEORECORDWIDGET_H #define VIDEORECORDWIDGET_H#include <QWidget>#include<QFileDialog>#include <QImage> #include <QLabel> #include <QTimer> #include <opencv2/opencv.hpp>using name…

Linux实验过程

答案截图获取&#xff0c;代写&#xff1a; https://laowangall.oss-cn-beijing.aliyuncs.com/studentall.pdf 基本任务&#xff1a; 1.Linux操作系统安装 2.vi文本编辑 3. Linux用户及文件管理命令 4. Linux权限管理命令 5. Linux网络服务 提高任务&#xff1a; 1、Li…

DIY蓝牙键盘(1) - 理解 键盘报文(免费)

DIY蓝牙键盘(1) - 理解键盘报文 1. 键盘报文体验 一个键盘对于用户的体验是&#xff0c;用户按按键A他能看到字母A会在主机上显示出来。那这是如何实现的&#xff1f; 其实很简单&#xff0c;只要键盘发送下面的两个报文给主机&#xff0c;字母A就能在主机上显示出来。 (1)…

数据结构——图的应用(最小生成树,最短路径,拓扑排序,关键路径)

目录 1.最小生成树 1.概念回顾——生成树 2.最小生成树概念 2.构造最小生成树 1.MST性质 2.Prim算法 3.Kruskal 算法 4.两种算法比较 3.最短路径 1.两点间最短路径 2.某源点到其它各点最短路径 3.单源最短路径——用Dijkstra算法 4.所有顶点间的最短路径…

Flask Python:模糊查询filter和filter_by,数据库多条件查询

数据库&#xff08;sqlalchemy&#xff09;多条件查询 前言一、filter、filter_by实现过滤查询1、filter_by()基础查询并且查询&#xff08;多条件查询&#xff09; 2、filter()like&#xff1a;模糊查询and&#xff1a;并且查询or&#xff1a;或者查询 二、all(),first(),get(…

【一站式学会Kotlin】第一节 kotlin 介绍

作者介绍&#xff1a; 百度资深Android工程师T6&#xff0c;在百度任职7年半。 目前&#xff1a;成立赵小灰代码工作室&#xff0c;欢迎大家找我开发Android、微信小程序、鸿蒙项目。 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默。给大家…

中文Mistral模型介绍(Chinese-Mistral)——中文大语言模型

中文Mistral简介 Chinese-Mistral由清华大学地学系地球空间信息科学实验室开发。 该模型基于Mistral发布的Mistral-7B-v0.1训练得到。首先进行中文词表扩充&#xff0c;然后采用实验室提出的PREPARED训练框架&#xff08;under review&#xff09;在中英双语语料上进行增量预训…

RUST语言基本数据类型认识

1.RUST的基本数据类型参考: 2.使用RUST数据类型声明变量并赋值: let a:i81;//8位有符号整数let a1:u82;//8位无符号整数let b:i161;//16位有符号整数let b1:u162;//16位无符号整数let c:i321;//32位有符号整数let c1:u322;//32位无符号整数let d:i641;//64位有符号整数let d1:u…

C#编写MQTT客户端软件

主要参考C#MQTT编程06--MQTT服务器和客户端(winform版)_c#mqttserver-CSDN博客 但由于使用的.NET版本和MQTT库版本存在差异&#xff0c;因此有些不同。 MQTT协议内容在此不做描述&#xff0c;仅介绍VS使用C#的实现过程。本次使用VS2015&#xff0c;.netframwork4.6。 C#语言本身…

SQL server 查询数据库中所有的表名及行数

SQL server 查询数据库中所有的表名及行数 select a.name,b.rows from sysobjects as ainner join sysindexes as bon a.id b.id where (a.type u)and (b.indid in (0, 1)) and b.rows<50 and b.rows>20 order by a.name, b.rows desc;

Express框架搭建项目 node.js

文章目录 引言Express框架介绍express安装环境准备写一个简单的项目展示 文章总结 引言 Express是一个基于Node.js平台的轻量级Web应用框架&#xff0c;它提供了简洁的API和丰富的功能&#xff0c;使得开发者能够快速地构建Web服务器和API。本文将带领大家从零开始&#xff0c…

GDAL源码剖析(六)之GDAL开发及其调试

GDAL源码剖析&#xff08;六&#xff09;之GDAL开发及其调试-CSDN博客 一、简单的调用 关于GDAL的使用&#xff0c;网上的资料都很多&#xff0c;主要还是要熟悉GDAL的组织结构&#xff0c;类以及类的函数等&#xff0c;熟悉了&#xff0c;使用GDAL就不在话下了。最常用的就是…

苹果手表Apple Watch录了两个半小时的录音,却只能播放4秒,同步到手机也一样,还能修复好吗?

好多人遇到这个情况&#xff0c;用苹果手表Apple Watch录音&#xff0c;有的录1个多小时&#xff0c;有的录了3、4小时&#xff0c;甚至更长时间&#xff0c;因为手表没电&#xff0c;忘记保存等原因造成录音损坏&#xff0c;都是只能播放4秒&#xff0c;同步到手机也一样&…

JavaScript基础代码练习之冒泡排序

一、要求对一个数组进行冒泡排序&#xff0c;并将排序后的结果输出到控制台。在代码中&#xff0c;数组 arr 包含了一组数字&#xff0c;然后使用嵌套的循环来进行冒泡排序。 二、编写代码 <!DOCTYPE html> <html lang"en"><head><meta chars…

软件测试用例(2)

具体的设计方法 -- 黑盒测试 因果图 因果图是一种简化的逻辑图, 能直观地表明程序的输入条件(原因)和输出动作(结果)之间的相互关系. 因果图法是借助图形来设计测试用例的一种系统方法, 特别适用于被测试程序具有多种输入条件, 程序的输出又依赖于输入条件的各种情况. 因果图…

深挖苹果Find My技术,伦茨科技ST17H6x芯片赋予产品功能

苹果发布AirTag发布以来&#xff0c;大家都更加注重物品的防丢&#xff0c;苹果的 Find My 就可以查找 iPhone、Mac、AirPods、Apple Watch&#xff0c;如今的Find My已经不单单可以查找苹果的设备&#xff0c;随着第三方设备的加入&#xff0c;将丰富Find My Network的版图。产…

fastadmin学习08-查询数据渲染到前端

index.php查询&#xff0c;这个是前台的index.php public function index() {$slideImgs Db::name("slideimg")->where("status",,normal)->limit(5)->order(sort,desc)->select();$productList Db::name("product")->where(…

python的垃圾回收

引用计数器为主&#xff0c;标记清除和分代回收为辅 1 引用计数器 在python程序运行时&#xff0c;会根据数据类型的不同找到其对应的结构体&#xff0c;根据结构体中的字段来进行创建相关的数据&#xff0c;然后将对象添加到refchain双像链表中&#xff0c;每个对象中的ob_re…