【C++:异常】C++ 异常处理完全指南:从理论到实践,深入理解栈展开与最佳实践

【C++:异常】C++ 异常处理完全指南:从理论到实践,深入理解栈展开与最佳实践

在这里插入图片描述


🎬 个人主页艾莉丝努力练剑
专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录
Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平


🎬 艾莉丝的简介:

在这里插入图片描述

🎬 艾莉丝的C++专栏简介:

在这里插入图片描述

文章目录


C++学习阶段的三个参考文档

看库文件(非官方文档):Cplusplus.com

在这里插入图片描述

这个文档在C++98、C++11时候还行,之后就完全没法用了……

准官方文档(同步更新)——还 可以看语法C++准官方参考文档

在这里插入图片描述


这个行,包括C++26都同步了,我们以后主要会看这个。

官方文档(类似论坛):Standard C++

在这里插入图片描述


这个网站上面会有很多大佬,类似于论坛。


在这里插入图片描述

1 ~> 异常的概念

在C语言里面,异常的处理机制——通过错误码的形式处理错误,比较麻烦,如下图所示——

在这里插入图片描述

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

C语言主要通过错误码的形式处理错误,错误码本质就是对错误信息进行分类编号,拿到错误码以后还要去查询错误信息,比较麻烦。异常时抛出一个对象(可以抛出任何类型的异常),这个对象可以函数更全面的各种信息(包含各种各样的信息)。


2 ~> 异常的使用层

2.1 异常的抛出和捕获

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

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

当throw执行时,throw后面的语句将不再被执行。程序的执行从throw位置跳到与之匹配的catch模块,catch可能是同一函数中的一个局部的catch,也可能是调用链中另一个函数中的catch,控制权从throw位置转移到了catch位置。

在这里插入图片描述

抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个局部对象,所以会生成一个拷贝对象,这个拷贝的对象会在catch子句后销毁(这里的处理类似于函数的传值返回)。


2.2 栈展开

2.2.1 理论

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

如果当前函数中没有try/catch子句,或者有try/catch子句但是类型不匹配,则退出当前函数,继续在外层调用函数链中查找,上述查找的catch过程被称为栈展开。

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

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

在这里插入图片描述

2.2.2 最佳实践

注意: e.what(); 返回包含异常的字符串。

在这里插入图片描述

上图中,[ LINE ] 的作用:获取你是哪个位置的宏

在这里插入图片描述

2.3 查找匹配的处理代码

2.3.1 抛出对象和catch一般是类型完全匹配的

一般情况下抛出对象和catch是类型完全匹配的,如果有多个类型匹配的,就选择离它位置更近的那个(类型匹配时遵循“就近原则”)。

但是也有一些例外,允许从非常量向常量的类型转换,也就是权限缩小;允许数组转换成指向数组元素类型的指针,函数被转换成指向函数的指针;允许从派生类向基类类型的转换,这个点非常实用,实际中继承体系基本都是用这个方式设计的。

如果到main函数,异常仍旧没有被匹配就会终止程序,不是发生严重错误的情况下,我们是不期望程序终止的,所以一般main函数中最后都会使用catch(…),它可以捕获任意类型的异常,但是我们不知道异常错误是什么。

2.3.2 最佳实践

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.4 异常重新抛出

2.4.1 概念

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

2.4.2 最佳实践

在这里插入图片描述
在这里插入图片描述

2.5 异常安全问题

2.5.1 注意事项

1、异常抛出后,后面的代码就不再执行,前面申请了资源(内存、锁等),后面进行释放,但是中间可能会抛异常就会导致资源没有释放,这里由于异常就引发了资源泄漏,产生安全性的问题。中间我们需要捕获异常,释放资源后面再重新抛出,当然后面智能指针章节讲的RAII方式解决这种问题是更好的。

在这里插入图片描述

2、其次析构函数中,如果抛出异常也要谨慎处理,比如析构函数要释放10个资源,释放到第5个时抛出异常,则也需要捕获处理,否则后面的5个资源就没释放,也资源泄漏了。《EffctiveC++》中的第8个条款也专门讲了这个问题,别让异常逃离析构函数。

2.5.2 最佳实践

在这里插入图片描述

2.6 异常规范

2.6.1 理论

对于用户和编译器而言,预先知道某个程序会不会抛出异常大有裨益,知道某个函数是否会抛出异常有助于简化调用函数的代码。

C++98中函数参数列表的后面接throw),表示函数不抛异常,函数参数列表的后面接throw(类型1,类型2…)表示可能会抛出多种类型的异常,可能会抛出的类型用逗号分割

C++98的方式这种方式过于复杂,实践中并不好用,C++11中进行了简化,函数参数列表后面加noexcept表示不会抛出异常,啥都不加表示可能会抛出异常。

编译器并不会在编译时检查noekcept,也就是说如果一个函数用noexcept修饰了,但是同时又包含了throw语句或者调用的函数可能会抛出异常,编译器还是会顺利编译通过的(有些编译器可能会报个警告)。但是一个声明了noexcept的函数抛出了异常,程序会调用terminate终止程序。

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

2.6.2 最佳实践

在这里插入图片描述
在这里插入图片描述

3 ~> 标准库的异常

标准库链接:std::exception

在这里插入图片描述


C++标准库也定义了一套自己的一套异常继承体系库,基类是exception,其它的都是它的派生类,所以我们日常写程序,需要在主函数捕获exception即可,要获取异常信息,调用what函数,what是一个虚函数,派生类可以重写。

在这里插入图片描述

C++11完整代码示例与实践演示

Test.cpp:

#define_CRT_SECURE_NO_WARNINGS1#include<iostream>usingnamespace std;#include<exception>//double Divide(int a, int b)//{// // 当b == 0时抛出异常 // if (b == 0)// {// //string s("Divide by zero condition!");// //throw s;//// throw exception("Divide by zero condition!");// }// else// {// return ((double)a / (double)b);// }//}////void Func()//{// try// {// int len, time;// cin >> len >> time;// cout << Divide(len, time) << endl;// }// catch (const exception& e)// {// cout << e.what() << endl;// }//// cout << "Func():" << __LINE__ << endl;//}////int main()//{// while (1)// {// try// {// Func();// }// catch (const string& s)// {// cout << s << endl;// }//// catch (const exception& e)// {// cout << e.what() << endl;// }//// catch (...) // 任意类型的对象// {// cout << "未知异常" << endl;// }//// cout << "main():" << __LINE__ << endl;// }//// return 0;//}#include<thread>// 延时功能// 一般大型项目才会使用异常,下面我们模拟设计一个服务的几个模块// 每个模块的继承都是Exception的派生类,每个模块可以添加自己的数据// 最后捕获时,我们捕获基类即可classException{public:Exception(const string& errmsg,int id):_errmsg(errmsg),_id(id){}virtual string what()const{return _errmsg;}intgetid()const{return _id;}protected: string _errmsg;int _id;};classSqlException:publicException{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:const string _sql;};classCacheException:publicException{public:CacheException(const string& errmsg,int id):Exception(errmsg, id){}virtual string what()const{ string str ="CacheException:"; str += _errmsg;return str;}};classHttpException:publicException{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:const string _type;};voidSQLMgr(){if(rand()%7==0){throwSqlException("权限不足",100,"select * from name = 张三");}else{ cout <<"SQLMgr 调用成功"<< endl;}}voidCacheMgr(){if(rand()%5==0){throwCacheException("权限不足",100);}elseif(rand()%6==0){throwCacheException("数据不存在",101);}else{ cout <<"CacheMgr 调用成功"<< endl;}SQLMgr();}voidHttpSever(){if(rand()%3==0){throwHttpException("申请资源不存在",100,"get");}elseif(rand()%4==0){throwHttpException("权限不足",101,"post");}else{ cout <<"HttpSever 调用成功"<< endl;}CacheMgr();}//int main()//{// srand(time(0));// while (1)// {// this_thread::sleep_for(chrono::seconds(1)); // 延迟1秒//// try// {// HttpSever();// }// catch (const Exception& e) // 这里捕获基类,基类对象和派生类对象都可以被捕获// {// // 多态调用// cout << e.what() << endl;// }//// catch (...) // 任意类型的对象// {// cout << "Unknown Exception" << endl;// }// }//// return 0;//}// 输出结果:// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpSever 调用成功// CacheException:权限不足// HttpSever 调用成功// CacheException:数据不存在// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpException:get : 申请资源不存在// HttpException:post : 权限不足// HttpException:get : 申请资源不存在// HttpException:post : 权限不足// HttpSever 调用成功// CacheException:权限不足// HttpSever 调用成功// CacheException:数据不存在// HttpException:get : 申请资源不存在// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpException:get : 申请资源不存在// HttpException:get : 申请资源不存在// HttpException:get : 申请资源不存在// HttpException:get : 申请资源不存在// HttpException:post : 权限不足// HttpException:post : 权限不足// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpSever 调用成功// CacheException:权限不足// HttpException:get : 申请资源不存在// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpException:post : 权限不足// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpSever 调用成功// CacheMgr 调用成功// SqlException:权限不足->select* from name = 张三// HttpException:post : 权限不足// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// . . . . . . .void_Sending(const string& s){if(rand()%2==0){throwHttpException("网络不稳定,发送失败",102,"put");}elseif(rand()%4==0){throwHttpException("你已经不是对象的好友,发送失败",103,"put");}else{ cout <<"发送成功"<< endl;}}// 网络不稳定,要求重试3次,均失败voidSendMsg(const string& s){for(size_t i =0; i <4; i++){try{_Sending(s);// 走到这里代表成功了,跳出循环break;}catch(const Exception& e){if(e.getid()==102){if(i ==3)throw; cout <<"开始第"<< i +1<<"重试"<< endl;}else{// 重新抛出异常//throw e;throw;}}}}//int main()//{// srand(time(0));// string str;// while (cin>>str)// {// try// {// SendMsg(str);// }// catch (const Exception& e) // 这里捕获基类,基类对象和派生类对象都可以被捕获// {// // 多态调用// cout << e.what() << endl << endl;// }//// catch (...) // 任意类型的对象// {// cout << "Unknown Exception" << endl;// }// }//// return 0;//}//double Divide(int a, int b) noexcept;//double Divide(int a, int b) throw(const char*);doubleDivide(int a,int b){// 当b == 0时抛出异常 if(b ==0){throw"Division by zero condition!";}return(double)a /(double)b;}voidFunc(){// 这里可以看出如果发生除0错误抛出异常,另外下面的array没有得到释放// 所以这里捕获异常后并不处理异常,异常还是交给外层处理,这里捕获了再重新抛出去int* array =newint[10];int len, time;//try//{// cout << Divide(len, time) << endl;//}//catch(...)//{// cout << "delete []" << array << endl;// delete[] array;// // 重新抛出,捕获到什么就抛出什么// throw;//} cout <<"delete[] "<< array << endl;delete[] array;}intmain(){//try//{// Func();//}//catch (const char* errmsg)//{// cout << errmsg << endl;//}//catch (...)//{// cout << "Unknown Exception" << endl;//}int i =0; cout <<noexcept(Divide(1,2))<< endl; cout<<noexcept(Divide(1,0))<< endl; cout <<noexcept(Func())<< endl; cout <<noexcept(++i)<< endl;return0;}// 输出结果:// 0// 0// 0// 1

结尾

uu们,本文的内容到这里就全部结束了,艾莉丝再次感谢您的阅读!

结语:希望对学习C++相关内容的uu有所帮助,不要忘记给博主“一键四连”哦!

往期回顾:

【C++:C++11收尾】解构C++可调用对象:从入门到精通,掌握function包装器与bind适配器包装器详解

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡૮₍ ˶ ˊ ᴥ ˋ˶₎ა

Read more

OpenClaw:打造你的私人 AI 助手,把 AI 变成你的数字管家

谈到 AI 助手,你会想到什么?ChatGPT、Siri、还是手机里的智能语音助手?这些云端服务固然强大,但它们有一个共同的弱点——你的数据都在别人的服务器上。 今天要介绍的 OpenClaw,是一款可以运行在你自己设备上的个人 AI 助手。它像是一个数字管家,通过 WhatsApp、Telegram、飞书、微信等多种渠道与你对话,帮你处理各种任务。 为什么要用本地 AI 助手? 数据隐私,由你掌控 在云时代,我们的对话、搜索、工作记录都存储在第三方服务器上。即使服务商承诺保护隐私,但"信任"这个词本身就带着风险。 OpenClaw 的理念很简单:数据留在我这儿,AI 逻辑你自己选。你可以在自己的电脑、服务器或私有云上运行它,所有会话记录都存储在本地。 一套系统,全平台覆盖 你可能有多个聊天工具:WhatsApp 用于国际沟通、

By Ne0inhk
告别“选择困难症”:我是如何用 AI Ping 实现大模型自由,还能省下 50% 成本的?

告别“选择困难症”:我是如何用 AI Ping 实现大模型自由,还能省下 50% 成本的?

告别“选择困难症”:我是如何用 AI Ping 实现大模型自由,还能省下 50% 成本的? * 写在最前面 * 场景一:从“写脚本卡壳”到“批量生成” * 场景二:开发路上的“万能插头” * 使用感受 * 一点小建议与期待 * 写在最后 🌈你好呀!我是 是Yu欸🚀 感谢你的陪伴与支持~ 欢迎添加文末好友🌌 在所有感兴趣的领域扩展知识,不定期掉落福利资讯(*^▽^*) 写在最前面 版权声明:本文为原创,遵循 CC 4.0 BY-SA 协议。转载请注明出处。 在这个大模型“百花齐放”甚至“百模大战”的时代,作为一名既要写代码开发,又要频繁输出技术内容(写博文、做视频)的开发者,我每天最大的烦恼就是: “今天这个任务,

By Ne0inhk
AutoGPT+Python:让AI智能体自动完成复杂任务的终极指南

AutoGPT+Python:让AI智能体自动完成复杂任务的终极指南

AutoGPT+Python:让AI智能体自动完成复杂任务的终极指南 引言:在人工智能迈向自主化的新阶段,AutoGPT作为基于大语言模型(LLM)的自主智能体代表,正掀起一场让AI自己思考、自主执行的技术革命。当它遇上Python的全栈生态与极致灵活性,开发者不再只是调用AI接口,而是能深度定制专属智能体——让AI听懂自然语言、拆解复杂目标、调用外部工具、联网检索信息、迭代优化结果,独立完成从市场调研、内容创作、代码开发到自动化运维的全流程任务。 本文从核心原理、本地部署、Python实战、插件扩展、生产优化五大维度,手把手带你从0到1搭建可落地、可监控、可进化的AI智能体系统,不管是AI爱好者、全栈开发者还是创业者,都能靠这份指南,掌握下一代人机协作的核心生产力。 一、先搞懂:AutoGPT到底是什么? 传统ChatGPT类模型是被动应答,你问一句它答一句,需要人工一步步引导;而AutoGPT是自主智能体,你只给它一个最终目标,它就能自己完成: * 任务拆解:把复杂目标拆成可执行子步骤 * 自主决策:判断下一步该做什么、调用什么工具 * 记忆管理:短期记忆存上下文

By Ne0inhk
手把手教你:在 Windows 部署 OpenAkita 并接入飞书模块,实现真正能干活的本地 AI 助手

手把手教你:在 Windows 部署 OpenAkita 并接入飞书模块,实现真正能干活的本地 AI 助手

目 录 * 前言 * 第一章:为什么选 OpenAkita,而不是直接用 OpenClaw? * 1.1 当前 AI 助理的几个现实痛点 * 1.2 OpenAkita 的核心优势(对比 OpenClaw) * 1.3 谁最适合用 OpenAkita? * 第二章:Windows 下安装 OpenAkita(两种方案) * 2.1 准备工作 * 2.2 方案一:一键脚本安装(适合能接受 PowerShell 的用户) * 2.3 方案二:桌面安装包(最像普通软件,新手友好) * 第三章:配置蓝耘(Lanyun)平台 API 密钥

By Ne0inhk