news 2026/5/26 7:18:12

异常那些不为人知的秘密

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
异常那些不为人知的秘密

异常的概念

异常处理机制允许程序中独立开发的部分在运行时就出现的问题进行通信做出相应的处理,异常使得我们将问题的检测与解决问题的过程分开程序的一部分负责检测问题的出现,然后解决问题的任务传递给程序的另一部分,检测环节问题处理模块的不需要所有细节。

C语言主要通过错误码的形式处理错误,错误码本质就是对错误信息进行分类编号,拿到错误码以后还要去查询错误信息,比较麻烦。

异常时抛出一个对象,这个对象可以包含更全面的各种信息。

异常的抛出和捕获

程序出现问题时,我们通过抛出(throw)一个对象来引发异常,该对象当前的类型以及调用链决定了应该由哪个catch的处理代码来处理该异常。

被选中的处理代码是调用链中与该对象类型匹配且离抛出异常最近的哪一个。根据抛出对象的类型和内容,程序抛出异常部分告知异常处理部分到底发生了什么错误。

当throw执行时,throw后面的语句将不再执行程序的执行从throw位置跳到与之匹配的catch模块,catch可能是同一个函数的一个局部的catch,也可能是调用链上另一个函数的catch,控制权从throw转移到catch位置。含义:沿着调用链的的函数可能提早退出。2一旦程序开始执行异常处理程序,沿着调用链创建的对象都将销毁

抛出异常对象后会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个局部对象,所以会生成一个拷贝对象,这个拷贝对象会在catch子句后销毁。

栈展开

抛出异常后,程序暂停当前函数的执行,开始寻找与之匹配的catch子句,首先检查throw是否在try块内部,如果在查找与之匹配的catch语句,如果有匹配的则跳到catch的地方处理。

如果当前函数没有try/catch子句,或者有try/catch子句但是类型不匹配,则退出当前函数,继续在外层调用函数链中查找,这个过程就叫做栈展开

如果到达main函数,依旧没有找到匹配的catch子句,程序会调用terminate函数终止程序

如果找到匹配的catch子句处理后,catch子句代码会继续执行

double Divild(int a, int b) { try { if (b == 0) { string s("Divide by zero condition!"); throw s; } } catch(int errid) { cout << errid << endl; } return (double)a / (double)b; } void Func() { int left, right; cin >> left >> right; try { cout << Divild(left, right) << endl; } catch(const string& errmsg) { cout <<"Func()->" << errmsg << endl; } cout << __FUNCTION__ << ":" << __LINE__ << "⾏执⾏" << endl; } int main() { while (1) { try { Func(); } catch (const string& errmsg) { cout << "main->" << errmsg << endl; } } return 0; }

查找匹配的处理代码

一般情况下抛出的对象和catch是完全匹配的,如果有多个类型匹配,就选择离它位置最近的那个。

例外,允许从非常量向向量的类型转换(权限缩小),允许数组转换成指向数组元素类型的指针,函数被转换成指向函数的指针;允许派生类对象向基类类型的转换,继承体系用这个实现。

如果到main函数,异常仍旧没有被匹配就会终止程序,不发生严重错误,我们是不希望程序终止,所以在main函数最后都会使用 catch(...),它 可以捕获任意异常,但是不知道异常错误是什么

class Exception { public: Exception(const string& errmsg, int id) :_errmsg(errmsg) ,_id(id) { } virtual string what()const { return _errmsg; } int getid()const { return _id; } protected: string _errmsg; //错误信息 int _id; //错误编码 }; class HttpException:public Exception { public: HttpException(const string& errmsg, int id,const string& type) :Exception(errmsg,id) ,_type(type) { } virtual string what()const { string str="HttpException"; str += _type; str += ':'; str += _errmsg; return str; } private: //string _errmsg; //错误信息 //int _id; //错误编码 const string _type;//错误类型 }; class SQLException :public Exception { public: SQLException(const string& errmsg, int id, const string& sql) :Exception(errmsg, id) , _sql(sql) { } virtual string what()const { string str = "SQLException"; str += _errmsg; str += '->'; str += _sql; return str; } private: //string _errmsg; //错误信息 //int _id; //错误编码 const string _sql;//错误类型 }; class CacheException :public Exception { public: CacheException(const string& errmsg, int id) :Exception(errmsg, id) { } virtual string what()const { string str = "CacheException:"; str += _errmsg; return str; } }; void SQLMgr() { if (rand() % 7 == 0) { throw SQLException("权限不⾜", 100, "select * from name = '张三'"); } else { cout << "SQLMgr 调⽤成功" << endl; } } void CacheMgr() { if (rand() % 5 == 0) { throw CacheException("权限不⾜", 100); } else if (rand() % 6 == 0) { throw CacheException("数据不存在", 101); } else { cout << "CacheMgr 调⽤成功" << endl; } SQLMgr(); } void HttpServer() { if (rand() % 3 == 0) { throw HttpException("请求资源不足", 100, "get"); } else if(rand() % 4 == 0) { throw HttpException("权限不足", 101, "post"); } else { cout << "HttpServer调用成功" << endl; } CacheMgr(); } #include<thread> // 每个模块的继承都是Exception的派⽣类,每个模块可以添加⾃⼰的数据 // 最后捕获时,我们捕获基类就可以 //int main() //{ // srand(time(nullptr)); // while (1) // { // this_thread::sleep_for(chrono::seconds(1)); // // try // { // HttpServer(); // } // catch (const Exception& e) // { // cout << e.what() << endl; // } // catch (...) // { // cout << "unknowed error" << endl; // } // } // return 0; //}

异常重新抛出

有时catch到一个异常对象后,需要对错误进行分类,其中的某种错误需要进行特殊处理,其他错误则重新抛出异常给外层的调用链处理捕获异常后重新抛出,直接throw;就可以把捕获的异常重新抛出。

void _Sendmsg(const string& str) { if (rand() % 2 == 0) { throw HttpException("网络不稳定,发送失败", 102, "put"); } else if (rand() % 7 == 0) { throw HttpException("你已经不是对方好友,发送失败", 103, "put"); } else { cout << "发送成功" << endl; } } void Sendmsg(const string& str) { for (int i = 0; i < 4; i++) { try { _Sendmsg(str); } catch (const Exception& e) { if (e.getid() == 102) { if (i == 3) { cout << "*****************" << endl; throw; } cout << "开始第" << i + 1 << endl; } else { throw; } } } } //int main() //{ // srand(time(nullptr)); // // string str; // while (1) // { // // this_thread::sleep_for(chrono::seconds(1)); // // try // { // Sendmsg(str); // } // catch (const Exception& e) // { // cout << e.what() << endl << endl; // } // catch (...) // { // cout << "unknowed error" << endl; // } // } // return 0; //}

异常的安全问题

异常抛出以后,后面的代码就不再执行,前面申请了资源(内存,锁),后面进行释放,但是中间抛异常就会导致资源没有释放,引发资源泄露,产生安全性问题。中间我们需要捕获异常,释放资源后再重新抛出。(智能指针才是最优解)

其次析构函数,如果抛出异常也要小心,比如析构函数要释放10个资源,释放到第5个时抛出异常,则也需要捕获处理,否则后面5个资源就没释放,也资源泄露了

/////////////////////////////异常安全 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; }

异常规范

C++11在函数参数列表后面➕noexcept表示不会抛出异常啥都不加表示可能会抛出异常

编译器并不会在编译时检查noexcept,一个函数用noexcept修饰了以后,同时又包含了throw语句或者调用的函数可能会抛出异常,编译器还是会顺利通过。但是⼀个声明了noexcept的函数抛出了异常,程序会调⽤ terminate 终⽌程序

noexcept(表达式)还可以作为一个运算符去检测一个表达式释放会抛出异常可能则返回false,不会就返回true。

///////////////异常规范/////////////////////// double Divide(int a, int b) { // 当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; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/25 22:57:02

H3C防火墙Web登录实验

H3C防火墙Web登录实验 文章目录H3C防火墙Web登录实验一、背景二、实验拓扑图三、实验环境规划四、实验需求五、实验步骤第一步&#xff1a;在本机PC上创建微软环回适配器第二步&#xff1a;修改本机环回适配器的IP地址第三步&#xff1a;配置FW1&#xff0c;给FW1设置IP地址第四…

作者头像 李华
网站建设 2026/5/26 6:58:23

告别 “告警风暴” 与被动救火!8 大数据库监控维度提前规避 80% 故障

在数字化转型纵深推进的今天&#xff0c;数据库已成为支撑业务运转的核心基础设施。无论是电商平台的订单交易、金融机构的账务处理&#xff0c;还是政务系统的数据存储&#xff0c;数据库的稳定性与性能直接决定业务连续性和用户体验。权威数据显示&#xff0c;75%的严重业务中…

作者头像 李华
网站建设 2026/5/25 12:29:39

数据库监控实践中的六大常见误区与规避思路

在数据库运维的“江湖”里&#xff0c;监控系统不仅是DBA的眼睛&#xff0c;更是“保命”的护身符。然而&#xff0c;现实中有太多团队在建设数据库监控体系时&#xff0c;往往陷入了一些思维陷阱——钱花了&#xff0c;工具上了&#xff0c;故障发生时却依然手忙脚乱。真正成熟…

作者头像 李华
网站建设 2026/5/25 14:58:38

【c++进阶】在c++11之前的编译器的努力

关注我&#xff0c;学习c不迷路: 个人主页&#xff1a;爱装代码的小瓶子 专栏如下&#xff1a; c学习Linux学习 后续会更新更多有趣的小知识&#xff0c;关注我带你遨游知识世界 期待你的关注。 文章目录 第一章&#xff1a;C11前夜 - 编译器的"军阀混战"时代1.1…

作者头像 李华
网站建设 2026/5/25 10:06:24

全网热议!2025年高亮车灯升级品牌推荐榜单

2025年高亮车灯升级推荐榜单致力于为车主提供最新的灯光改装选择&#xff0c;特别是聚焦于高级汽车激光透镜的应用。这些高亮车灯不仅提升了汽车的外观&#xff0c;还能在各种复杂路况下提供卓越的照明效果。许多车型采用创新技术&#xff0c;如TIR棱镜&#xff0c;有效提升光学…

作者头像 李华