news 2026/6/10 19:26:21

异常那些不为人知的秘密

作者头像

张小明

前端开发工程师

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/6/10 9:35:20

选对语言,赢在起点!新手 C、Java、Python 指南uT#45篇

SQLAlchemy是Python中最流行的ORM&#xff08;对象关系映射&#xff09;框架之一&#xff0c;它提供了高效且灵活的数据库操作方式。本文将介绍如何使用SQLAlchemy ORM进行数据库操作。目录安装SQLAlchemy核心概念连接数据库定义数据模型创建数据库表基本CRUD操作查询数据关系操…

作者头像 李华
网站建设 2026/6/10 11:36:02

C++课后习题训练记录Day52

1.练习项目&#xff1a; 练习使用map函数及其常用函数 2.选择课程 在蓝桥云课中选择课程《16届蓝桥杯省赛无忧班&#xff08;C&C 组&#xff09;4期》&#xff0c;选择第STL”课程16并开始练习。 3.开始练习 &#xff08;1&#xff09;源码&#xff1a; #include<…

作者头像 李华
网站建设 2026/6/9 17:40:41

FastAPI 流式响应中,如何优雅处理客户端断连后的数据库操作?

问题出现过程1. 客户端发起流式对话请求我们从一个典型的流式对话接口开始。我们使用依赖注入来获取一个 SQLAlchemy 的 AsyncSession&#xff0c;在对话开始时创建消息&#xff0c;在对话结束后更新 AI 的回答。流式对话原始代码&#xff08;伪代码&#xff09;2. 客户端取消对…

作者头像 李华
网站建设 2026/6/9 23:48:29

【elementUI】form表单rules没生效

关键原则是&#xff1a; 中的 prop 值必须与验证规则对象中的键名完全一致。对于嵌套属性如 info.modelId&#xff0c;需要在两个地方都指定完整的路径。<template><el-form :model"editForm" ref"editRef" :rules"editFormRules">&…

作者头像 李华
网站建设 2026/6/10 11:42:59

天津到潍坊危险品物流运输公司 | 天津危化品专线直达潍坊 | 危险品仓储运输一体化

全链条服务覆盖天津至潍坊的危化品运输通道已形成成熟服务体系&#xff0c;覆盖全国34个省级行政区域&#xff0c;重点辐射京津冀、长三角及珠三角经济带。该线路支持医疗废弃物、腐蚀性化学品等9大类危险品运输&#xff0c;配套智能仓储系统实现货物分类存储与全流程溯源管理。…

作者头像 李华
网站建设 2026/6/10 11:40:34

学GIS开发,都应该了解的顺序!

01 学习人群一般来说&#xff0c;学习WebGIS开发的人员有两类。其中较为庞大的群体是3S&#xff08;GIS、RS、GNSS&#xff09;及相关专业的学生&#xff1b;另一类是计算机专业的学生2024年陆续也有一些其他专业的人士也对进入WebGIS开发行业感兴趣&#xff0c;这里不多赘述。…

作者头像 李华