news 2026/2/25 2:40:57

异常那些不为人知的秘密

作者头像

张小明

前端开发工程师

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/2/23 1:57:07

行业视角下的数据库监控演进:主动预防能力何以成为刚需

凌晨三点的告警电话刺耳地响起&#xff0c;屏幕上一片飘红的性能指标让DBA&#xff08;数据库管理员&#xff09;瞬间清醒&#xff0c;又一个不眠之夜在“救火”中开始了——这种场景曾是DBA工作的日常。深夜的“救火”场景&#xff0c;本质是传统被动响应运维模式的真实写照。…

作者头像 李华
网站建设 2026/2/23 2:55:41

​当年靠这个ASP.NET电子书城系统,我的毕业设计直接拿优!(附核心源码)​

谁懂啊!当年做毕业设计时,选了个 “电子书城系统”,没想到不仅完美解决了传统购书的痛点,还靠扎实的技术实现拿了优秀!今天把这份压箱底的开发笔记分享出来,包含技术选型、核心模块实现、踩坑实录,适合.NET 初学者练手,老程序员也能追忆当年的开发情怀~ 一、项目背景…

作者头像 李华
网站建设 2026/2/23 23:33:35

极坐标波束形成数据底跟踪算法详解

极坐标波束形成数据底跟踪算法详解 一、基本概念 1.1 底跟踪的定义 底跟踪&#xff08;Bottom Tracking&#xff09;是通过声学回波信号检测和跟踪海底位置的技术&#xff0c;主要用于&#xff1a; 测量船舶相对于海底的速度确定水深辅助水下导航定位补偿多普勒计程仪测量 …

作者头像 李华
网站建设 2026/2/8 7:43:13

【技术教程】TrustFlow 授权策略是怎么实现的?

打开链接即可点亮社区Star&#xff0c;照亮技术的前进之路。 Github 地址&#xff1a;https://github.com/secretflow/trustflow/ TrustFlow提供了一套简洁易懂的语法帮助用户对数据使用行为的授权进行描述。接下来我们会详细描述这套语法&#xff0c;并结合示例进行讲解。 …

作者头像 李华
网站建设 2026/2/22 21:02:55

丐版 OI 技巧 / 杂项部分总结 + 作者学习笔记

持久化区间修改区间查询线段树&#xff1a;SP11470 TTM - To the moon点击查看代码2. 有后效性的 dpCF24D Broken robot一般用高斯消元 求解。也可以多跑几遍朴素 dp 使误差降到可接受范围内。多跑几遍的代码3. P14402 [JOISC 2016] 危险的滑冰 / Dangerous Skating图论建模。思…

作者头像 李华