news 2026/4/23 18:19:05

异常那些不为人知的秘密

作者头像

张小明

前端开发工程师

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/4/23 16:06:14

DOM 与 BOM:前端开发者必须分清的两大核心对象

作为前端开发的基础&#xff0c;DOM 和 BOM 是 JavaScript 与浏览器交互的核心&#xff0c;但很多初学者容易将二者混淆 —— 前者操控网页内容&#xff0c;后者掌控浏览器本身。本文将从定义、核心作用、使用场景、核心对象等维度&#xff0c;彻底讲清 DOM 与 BOM 的区别与联系…

作者头像 李华
网站建设 2026/4/23 18:03:45

毕设项目 大数据B站数据分析可视化系统

文章目录 0 前言1 项目运行效果2 设计原理数据处理方案可视化呈现方案综合得分计算指标综合得分漏斗图游客画像完成度三连排行榜点赞、投币、收藏与白嫖的比例分析 3 最后 0 前言 &#x1f525;这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺…

作者头像 李华
网站建设 2026/4/23 17:53:35

毕设项目分享 基于深度学习的动物识别

文章目录0 前言1 背景2 算法原理2.1 动物识别方法概况2.2 常用的网络模型2.2.1 B-CNN2.2.2 SSD3 SSD动物目标检测流程4 实现效果5 部分相关代码5.1 数据预处理5.2 构建卷积神经网络5.3 tensorflow计算图可视化5.4 网络模型训练5.5 对猫狗图像进行2分类0 前言 今天学长向大家介…

作者头像 李华
网站建设 2026/4/23 13:02:20

(让 Java IA MCP 更简单 )Solon AI v3.7.2 发布

olon AI & MCP&#xff08;支持 LTS&#xff09;Solon AI & MCP &#xff0c;是 Solon 官方推出的 Java 智能体应用开发框架。旨在为 Java 开发者提供统一的接口抽象层&#xff0c;简化与 OpenAI、DeepSeek、QWen 等主流 AI 模型的集成流程&#xff0c;以及简化 Mcp 的…

作者头像 李华