【C++】异常

前言

本篇博客我们来看下C++有关异常的处理,了解下异常有关的知识

💓 个人主页:小张同学zkf

⏩ 文章专栏:C++

若有问题 评论区见📝

🎉欢迎大家点赞👍收藏⭐文章 ​

目录

1.异常的概念及使用

 1.1异常的概念

1.2异常的抛出和捕获

1.3栈展开

1.4查找匹配的处理代码

1.5异常重新抛出

 1.6异常安全问题

1.7异常规范

2.标准库的异常


1.异常的概念及使用

 1.1异常的概念

异常处理机制允许程序中独⽴开发的部分能够在运⾏时就出现的问题进⾏通信并做出相应的处理,
异常使得我们能够将问题的检测与解决问题的过程分开,程序的⼀部分负责检测问题的出现,然后
解决问题的任务传递给程序的另⼀部分,检测环节⽆须知道问题的处理模块的所有细节。
C语⾔主要通过错误码的形式处理错误,错误码本质就是对错误信息进⾏分类编号,拿到错误码以
后还要去查询错误信息,⽐较⿇烦。异常时抛出⼀个对象,这个对象可以函数更全⾯的各种信息。

1.2异常的抛出和捕获

程序出现问题时,我们通过抛出(throw)⼀个对象来引发⼀个异常,该对象的类型以及当前的调⽤
链决定了应该由哪个catch的处理代码来处理该异常。
被选中的处理代码是调⽤链中与该对象类型匹配且离抛出异常位置最近的那⼀个。根据抛出对象的
类型和内容,程序的抛出异常部分告知异常处理部分到底发⽣了什么错误。
当throw执⾏时,throw后⾯的语句将不再被执⾏。程序的执⾏从throw位置跳到与之匹配的catch
模块,catch可能是同⼀函数中的⼀个局部的catch,也可能是调⽤链中另⼀个函数中的catch,控
制权从throw位置转移到了catch位置。这⾥还有两个重要的含义:1、沿着调⽤链的函数可能提早
退出。2、⼀旦程序开始执⾏异常处理程序,沿着调⽤链创建的对象都将销毁。
抛出异常对象后,会⽣成⼀个异常对象的拷⻉,因为抛出的异常对象可能是⼀个局部对象,所以会
⽣成⼀个拷⻉对象,这个拷⻉的对象会在catch⼦句后销毁。(这⾥的处理类似于函数的传值返
回)

1.3栈展开

抛出异常后,程序暂停当前函数的执⾏,开始寻找与之匹配的catch⼦句,⾸先检查throw本⾝是否
在try块内部,如果在则查找匹配的catch语句,如果有匹配的,则跳到catch的地⽅进⾏处理。
如果当前函数中没有try/catch⼦句,或者有try/catch⼦句但是类型不匹配,则退出当前函数,继续
在外层调⽤函数链中查找,上述查找的catch过程被称为栈展开。
如果到达main函数,依旧没有找到匹配的catch⼦句,程序会调⽤标准库的 terminate 函数终⽌
程序。
如果找到匹配的catch⼦句处理后,catch⼦句代码会继续执⾏。

double Divide ( int a, int b)
{
try
{
// b == 0 时抛出异常
if (b == 0 )
{
string s ( "Divide by zero condition!" );
throw s;
}
else
{
return (( double )a / ( double )b);
}
}
catch ( int errid)
{
cout << errid << endl;
}
return 0 ;
}
void Func ()
{
int len, time;
cin >> len >> time;
try
{
cout << Divide (len, time) << endl;
}
catch ( const char * errmsg)
{
cout << errmsg << endl;
}
cout <<__FUNCTION__<< ":" << __LINE__ << " ⾏执⾏ " << endl;
}
int main ()
{
while ( 1 )
{
try
{
Func ();
}
catch ( const string& errmsg)
{
cout << errmsg << endl;
}
}
return 0 ;
}

1.4查找匹配的处理代码

⼀般情况下抛出对象和catch是类型完全匹配的,如果有多个类型匹配的,就选择离他位置更近的
那个。
但是也有⼀些例外,允许从⾮常量向常量的类型转换,也就是权限缩⼩;允许数组转换成指向数组
元素类型的指针,函数被转换成指向函数的指针;允许从派⽣类向基类类型的转换,这个点⾮常实
⽤,实际中继承体系基本都是⽤这个⽅式设计的。
如果到main函数,异常仍旧没有被匹配就会终⽌程序,不是发⽣严重错误的情况下,我们是不期望程序终⽌的,所以⼀般main函数中最后都会使⽤catch(...),它可以捕获任意类型的异常,但是是
不知道异常错误是什么。
1 # include <thread>
2
3 // ⼀般⼤型项⽬程序才会使⽤异常,下⾯我们模拟设计⼀个服务的⼏个模块
4 // 每个模块的继承都是 Exception 的派⽣类,每个模块可以添加⾃⼰的数据
5 // 最后捕获时,我们捕获基类就可以
6 class Exception
7 {
8 public :
9 Exception ( const string& errmsg, int id)
10 :_errmsg(errmsg)
11 , _id(id)
12 {}
13
14 virtual string what () const
15 {
16 return _errmsg;
17 }
18
19 int getid () const
20 {
21 return _id;
22 }
23
24 protected :
25 string _errmsg;
26 int _id;
27 };
28
29 class SqlException : public Exception
30 {
31 public :
32 SqlException ( const string& errmsg, int id, const string& sql)
33 : Exception (errmsg, id)
34 , _sql(sql)
35 {}
36
37 virtual string what () const
38 {
39 string str = "SqlException:" ;
40 str += _errmsg;
41 str += "->" ;
42 str += _sql;
43 return str;
44 }
45 private :
46 const string _sql;
47 };
48 49 class CacheException : public Exception
50 {
51 public :
52 CacheException ( const string& errmsg, int id)
53 : Exception (errmsg, id)
54 {}
55
56 virtual string what () const
57 {
58 string str = "CacheException:" ;
59 str += _errmsg;
60 return str;
61 }
62 };
63
64 class HttpException : public Exception
65 {
66 public :
67 HttpException ( const string& errmsg, int id, const string& type)
68 : Exception (errmsg, id)
69 , _type(type)
70 {}
71
72 virtual string what () const
73 {
74 string str = "HttpException:" ;
75 str += _type;
76 str += ":" ;
77 str += _errmsg;
78 return str;
79 }
80
81 private :
82 const string _type;
83 };
84
85 void SQLMgr ()
86 {
87 if ( rand () % 7 == 0 )
88 {
89 throw SqlException ( " 权限不⾜ " , 100 , "select * from name = ' 张三 '" );
90 }
91 else
92 {
93 cout << "SQLMgr 调⽤成功 " << endl;
94 }
95 }
96
97 void CacheMgr ()
98 {
99 if ( rand () % 5 == 0 )
100 {
101 throw CacheException ( " 权限不⾜ " , 100 );
102 }
103 else if ( rand () % 6 == 0 )
104 {
105 throw CacheException ( " 数据不存在 " , 101 );
106 }
107 else
108 {
109 cout << "CacheMgr 调⽤成功 " << endl;
110 }
111
112 SQLMgr ();
113 }
114
115 void HttpServer ()
116 {
117 if ( rand () % 3 == 0 )
118 {
119 throw HttpException ( " 请求资源不存在 " , 100 , "get" );
120 }
121 else if ( rand () % 4 == 0 )
122 {
123 throw HttpException ( " 权限不⾜ " , 101 , "post" );
124 }
125 else
126 {
127 cout << "HttpServer 调⽤成功 " << endl;
128 }
129
130 CacheMgr ();
131 }
132
133
134 int main ()
135 {
136 srand ( time ( 0 ));
137
138 while ( 1 )
139 {
140 this_thread:: sleep_for (chrono:: seconds ( 1 ));
141
142 try
143 {
144 HttpServer ();
145 }
146 catch ( const Exception& e) // 这⾥捕获基类,基类对象和派⽣类对象都可以被
捕获
147 {
148 cout << e. what () << endl;
149 }
150 catch (...)
151 {
152 cout << "Unkown Exception" << endl;
153 }
154 }
155
156 return 0 ;
157 }

1.5异常重新抛出

有时catch到⼀个异常对象后,需要对错误进⾏分类,其中的某种异常错误需要进⾏特殊的处理,其他错误则重新抛出异常给外层调⽤链处理。捕获异常后需要重新抛出,直接 throw; 就可以把捕获的对象直接抛出。
1 // 下⾯程序模拟展⽰了聊天时发送消息,发送失败补货异常,但是可能在
2 // 电梯地下室等场景⼿机信号不好,则需要多次尝试,如果多次尝试都发
3 // 送不出去,则就需要捕获异常再重新抛出,其次如果不是⽹络差导致的
4 // 错误,捕获后也要重新抛出。
5 void _SeedMsg( const string& s)
6 {
7 if ( rand () % 2 == 0 )
8 {
9 throw HttpException ( " ⽹络不稳定,发送失败 " , 102 , "put" );
10 }
11 else if ( rand () % 7 == 0 )
12 {
13 throw HttpException ( " 你已经不是对象的好友,发送失败 " , 103 , "put" );
14 }
15 else
16 {
17 cout << " 发送成功 " << endl;
18 }
19 }
20
21 void SendMsg ( const string& s)
22 {
23 // 发送消息失败,则再重试 3
24 for ( size_t i = 0 ; i < 4 ; i++)
25 {
26 try
27 {
28 _SeedMsg(s);
29 break ;
30 }
31 catch ( const Exception& e)
32 {
33 // 捕获异常, if 中是 102 号错误,⽹络不稳定,则重新发送
34 // 捕获异常, else 中不是 102 号错误,则将异常重新抛出
35
36 if (e. getid () == 102 )
37 {
38 // 重试三次以后否失败了,则说明⽹络太差了,重新抛出异常
39 if (i == 3 )
40 throw ;
41
42 cout << " 开始第 " << i + 1 << " 重试 " << endl;
43 }
44 else
45 {
46 throw ;
47 }
48 }
49 }
50 }
51
52 int main ()
53 {
54 srand ( time ( 0 ));
55
56 string str;
57 while (cin >> str)
58 {
59 try
60 {
61 SendMsg (str);
62 }
63 catch ( const Exception& e)
64 {
65 cout << e. what () << endl << endl;
66 }
67 catch (...)
68 {
69 cout << "Unkown Exception" << endl; }
70}
71return 0 ;
72}

 1.6异常安全问题

异常抛出后,后⾯的代码就不再执⾏,前⾯申请了资源(内存、锁等),后⾯进⾏释放,但是中间可
能会抛异常就会导致资源没有释放,这⾥由于异常就引发了资源泄漏,产⽣安全性的问题。中间我
们需要捕获异常,释放资源后⾯再重新抛出,当然后⾯智能指针章节讲的RAII⽅式解决这种问题是
更好的。
其次析构函数中,如果抛出异常也要谨慎处理,⽐如析构函数要释放10个资源,释放到第5个时抛
出异常,则也需要捕获处理,否则后⾯的5个资源就没释放,也资源泄漏了。《Effctive C++》第8
个条款也专⻔讲了这个问题,别让异常逃离析构函数。
double Divide ( 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 << Divide (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;
}
catch ( const exception& e)
{
cout << e. what () << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
return 0 ;
}

1.7异常规范

对于⽤⼾和编译器⽽⾔,预先知道某个程序会不会抛出异常⼤有裨益,知道某个函数是否会抛出异
常有助于简化调⽤函数的代码。
C++98中函数参数列表的后⾯接throw(),表⽰函数不抛异常,函数参数列表的后⾯接throw(类型1,
类型2...)表⽰可能会抛出多种类型的异常,可能会抛出的类型⽤逗号分割。
C++98的⽅式这种⽅式过于复杂,实践中并不好⽤,C++11中进⾏了简化,函数参数列表后⾯加
noexcept 表⽰不会抛出异常,啥都不加表⽰可能会抛出异常。
编译器并不会在编译时检查noexcept,也就是说如果⼀个函数⽤noexcept修饰了,但是同时⼜包
含了throw语句或者调⽤的函数可能会抛出异常,编译器还是会顺利编译通过的(有些编译器可能会
报个警告)。但是⼀个声明了noexcept的函数抛出了异常,程序会调⽤ terminate 终⽌程序。
noexcept(expression)还可以作为⼀个运算符去检测⼀个表达式是否会抛出异常,可能会则返回
false,不会就返回true。
// C++98
// 这⾥表⽰这个函数只会抛出 bad_alloc 的异常
void * operator new (std:: size_t size) throw (std::bad_alloc);
// 这⾥表⽰这个函数不会抛出异常
void * operator delete (std:: size_t size, void * ptr) throw ();
// C++11
size_type size () const noexcept ;
iterator begin () noexcept ;
const_iterator begin () const noexcept ;
double Divide ( int a, int b) noexcept
{
// b == 0 时抛出异常
if (b == 0 )
{
throw "Division by zero condition!" ;
}
return ( double )a / ( double )b;
}
int main ()
{
try
{
int len, time;
cin >> len >> time;
cout << Divide (len, time) << endl;
}
catch ( const char * errmsg)
{
cout << errmsg << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
int i = 0 ;
cout << noexcept ( Divide ( 1 , 2 )) << endl;
cout << noexcept ( Divide ( 1 , 0 )) << endl;
cout << noexcept (++i) << endl;
return 0 ;
}


2.标准库的异常

exception - C++ Reference

C++标准库也定义了⼀套⾃⼰的⼀套异常继承体系库,基类是exception,所以我们⽇常写程序,需要在主函数捕获exception即可,要获取异常信息,调⽤what函数,what是⼀个虚函数,派⽣类可以重写。


结束语 

C++异常有关方面知识点总结完毕,对于异常容易产生的内存泄露问题,我们可以用智能指针来解决,下片博客我们来看看智能指针相关知识

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

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

相关文章

操作系统—进程与线程

补充知识 PSW程序状态字寄存器PC程序计数器&#xff1a;存放下一条指令的地址IR指令寄存器&#xff1a;存放当前正在执行的指令通用寄存器&#xff1a;存放其他一些必要信息 进程 进程&#xff1a;进程是进程实体的运行过程&#xff0c;是系统进行资源分配和调度的一个独立单位…

NIO--ByteBuffer组件

文章目录 概述ByteBuffer 正确使用姿势ByteBuffer 结构ByteBuffer 常见方法分配空间向 buffer 写入数据从 buffer 读取数据mark 和 reset字符串与 ByteBuffer 互转Scattering ReadsGathering Writes ⚠️ Buffer 的线程安全 概述 ByteBuffer就是缓冲区&#xff0c;作为channel…

软件工程的熵减:AI如何降低系统复杂度

软件开发的世界&#xff0c;如同一个不断膨胀的宇宙。随着功能的增加和时间的推移&#xff0c;代码库越来越庞大&#xff0c;系统复杂度也随之水涨船高。代码膨胀、维护困难、开发效率低下等问题困扰着无数开发者。这不禁让人联想到物理学中的“熵增”原理——一个孤立系统的熵…

xss闯关

BUU上的[第二章 web进阶]XSS闯关 第一关 第一关很简单&#xff0c;没有任何过滤&#xff0c;直接输入&#xff1a;<script>alert()</script>即可。 第二关 第二关可以输入xss和<script>alert()</script>分别查看页面源代码&#xff0c;看看哪里变了…

Postman接口测试:postman设置接口关联,实现参数化

postman设置接口关联 在实际的接口测试中&#xff0c;后一个接口经常需要用到前一个接口返回的结果&#xff0c; 从而让后一个接口能正常执行&#xff0c;这个过程的实现称为关联。 在postman中实现关联操作的步骤如下&#xff1a; 1、利用postman获取上一个接口指定的返回值…

从算法到落地:DeepSeek如何突破AI工具的同质化竞争困局

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;Linux网络编程 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 ​ Linux网络编程笔记&#xff1a; https://blog.cs…

【通俗易懂说模型】反向传播(附多元回归与Softmax函数)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;深度学习_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2. …

电脑黑屏按什么键恢复?电脑黑屏的解决办法

电脑黑屏的原因有很多&#xff0c;可能是硬件、软件、系统或者病毒等方面造成的。那么&#xff0c;当我们遇到电脑黑屏时&#xff0c;应该怎么做呢&#xff1f;有没有什么快捷的方法可以恢复正常呢&#xff1f;本文将为您介绍一些常见的电脑黑屏情况及其解决办法。 一、电脑开机…

多智能体协作架构模式:驱动传统公司向AI智能公司转型

前言 在数字化浪潮的席卷下&#xff0c;传统公司的运营模式正面临着前所未有的挑战。随着市场竞争的日益激烈&#xff0c;客户需求的快速变化以及业务复杂度的不断攀升&#xff0c;传统公司在缺乏 AI 技术支撑的情况下&#xff0c;暴露出诸多痛点。在决策层面&#xff0c;由于…

CNN 卷积神经网络处理图片任务 | PyTorch 深度学习实战

前一篇文章&#xff0c;学习率调整策略 | PyTorch 深度学习实战 本系列文章 GitHub Repo: https://github.com/hailiang-wang/pytorch-get-started CNN 卷积神经网络 CNN什么是卷积工作原理深度学习的卷积运算提取特征不同特征核的效果比较卷积核感受野共享权重池化 示例源码 …

云上考场微信小程序的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

Intellij IDEA如何查看当前文件的类

快捷键&#xff1a;CtrlF12&#xff0c;我个人感觉记快捷键很麻烦&#xff0c;知道具体的位置更简单&#xff0c;如果忘了快捷键&#xff08;KeyMap&#xff09;看一下就记起来了&#xff0c;不需要再Google or Baidu or GPT啥的&#xff0c;位置&#xff1a;Navigate > Fi…

支持多种网络数据库格式的自动化转换工具——VisualXML

一、VisualXML软件介绍 对于DBC、ARXML……文件的编辑、修改等繁琐操作&#xff0c;WINDHILL风丘科技开发的总线设计工具——VisualXML&#xff0c;可轻松解决这一问题&#xff0c;提升工作效率。 VisualXML是一个强大且基于Excel表格生成多种网络数据库文件的转换工具&#…

Python Pandas(5):Pandas Excel 文件操作

Pandas 提供了丰富的 Excel 文件操作功能&#xff0c;帮助我们方便地读取和写入 .xls 和 .xlsx 文件&#xff0c;支持多表单、索引、列选择等复杂操作&#xff0c;是数据分析中必备的工具。 操作方法说明读取 Excel 文件pd.read_excel()读取 Excel 文件&#xff0c;返回 DataF…

查看云机器的一些常用配置

云原生学习路线导航页&#xff08;持续更新中&#xff09; kubernetes学习系列快捷链接 Kubernetes架构原则和对象设计&#xff08;一&#xff09;Kubernetes架构原则和对象设计&#xff08;二&#xff09;Kubernetes架构原则和对象设计&#xff08;三&#xff09;Kubernetes常…

网站改HTTPS方法

默认的网站建设好后打开的样子那看起来像是钓鱼网站&#xff0c;现在的浏览器特别只能&#xff0c;就是你新买来的电脑默认的浏览器同样也会出现这样“不安全”提示。 传输协议启动了向全球用户安全传输网页内容的流程。然而&#xff0c;随着HTTPS的推出&#xff0c;传输协议通…

ssti学习笔记(服务器端模板注入)

目录 一&#xff0c;ssti是什么 二&#xff0c;原理 所谓模板引擎&#xff08;三列&#xff0c;可滑动查看&#xff09; 三&#xff0c;漏洞复现 1&#xff0c;如何判断其所属的模板引擎&#xff1f; 2&#xff0c;判断清楚后开始注入 &#xff08;1&#xff09;Jinja2&a…

解决基于FastAPI Swagger UI的文档打不开的问题

基于FastAPI Swagger UI的文档链接/docs和/redoc在没有外网的状态下无法打开&#xff0c;原因是Swagger依赖的JS和CSS来自CDN。 https://cdn.jsdelivr.net/npm/swagger-ui-dist5/swagger-ui-bundle.js https://cdn.jsdelivr.net/npm/swagger-ui-dist5/swagger-ui.css https://…

07苍穹外卖之redis缓存商品、购物车(redis案例缓存实现)

课程内容 缓存菜品 缓存套餐 添加购物车 查看购物车 清空购物车 功能实现&#xff1a;缓存商品、购物车 效果图&#xff1a; 1. 缓存菜品 1.1 问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压…

UML学习

定义&#xff1a;UML是一种用于软件系统分析和设计的标准化建模语言。 作用&#xff1a;用于描述系统的结构、行为、交互等。共定义了10种,并分为4类 ①用例图 user case diagram : 从外部用户的角度描述系统的功能,并指出功能的执行者. 静态图(②类图 class diagram ③,对象…