【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

AI绘画R18提示词实战指南:从基础原理到安全实践

快速体验 在开始今天关于 AI绘画R18提示词实战指南:从基础原理到安全实践 的探讨之前,我想先分享一个最近让我觉得很有意思的全栈技术挑战。 我们常说 AI 是未来,但作为开发者,如何将大模型(LLM)真正落地为一个低延迟、可交互的实时系统,而不仅仅是调个 API? 这里有一个非常硬核的动手实验:基于火山引擎豆包大模型,从零搭建一个实时语音通话应用。它不是简单的问答,而是需要你亲手打通 ASR(语音识别)→ LLM(大脑思考)→ TTS(语音合成)的完整 WebSocket 链路。对于想要掌握 AI 原生应用架构的同学来说,这是个绝佳的练手项目。 从0到1构建生产级别应用,脱离Demo,点击打开 从0打造个人豆包实时通话AI动手实验 AI绘画R18提示词实战指南:从基础原理到安全实践 背景痛点分析 1. 伦理风险与合规挑战 在AI绘画领域,R18内容创作面临着多重挑战。平台审核机制日益严格,违规内容可能导致账号封禁甚至法律风险。同时,不同地区对数字内容的法律界定存在差异,开发者需要特别注意合规边界。

By Ne0inhk

RMBG-2.0多任务协同方案:接入Stable Diffusion工作流,生成→抠图→合成一体化

RMBG-2.0多任务协同方案:接入Stable Diffusion工作流,生成→抠图→合成一体化 1. 为什么抠图成了AI图像工作流的“卡点”? 你有没有遇到过这样的场景:用Stable Diffusion生成了一张绝美的角色立绘,但背景太杂乱,想换到电商详情页却卡在了抠图环节?手动PS耗时半小时,AI在线工具又担心图片上传泄露隐私,还动不动就崩掉——毛发边缘糊成一片,玻璃杯透明感全无,甚至把飘动的发丝直接切掉。 这不是个别现象。大量设计师、内容创作者、电商运营者反馈:生成容易,落地难;模型很炫,流程断在抠图这一步。 而RMBG-2.0(BiRefNet)的出现,正在悄悄改变这个局面。它不是又一个“差不多能用”的抠图工具,而是首个真正意义上能无缝嵌入本地AI图像工作流的高精度、低延迟、零隐私风险抠图引擎。它不只解决“能不能抠”,更解决“抠完怎么用”——直接对接SD WebUI、ComfyUI、乃至自定义Python脚本,让“生成→

By Ne0inhk

手把手教你部署Z-Image-Turbo,5分钟搞定AI绘画环境

手把手教你部署Z-Image-Turbo,5分钟搞定AI绘画环境 你是否还在为部署文生图模型时漫长的权重下载、复杂的依赖配置而头疼?现在,这一切都可以结束了。本文将带你5分钟内完成Z-Image-Turbo的完整部署,无需等待下载、不用手动安装依赖,真正实现“开箱即用”的AI绘画体验。 我们将使用预置了完整32.88GB模型权重的专用镜像,一键启动即可生成1024×1024高清图像,仅需9步推理,速度快到惊人。无论你是AI绘画新手,还是想快速测试效果的技术人员,这篇文章都能让你立刻上手。 准备好了吗?让我们开始吧。 1. 镜像简介:为什么选择Z-Image-Turbo? 1.1 模型核心优势 Z-Image-Turbo 是阿里达摩院基于 DiT(Diffusion Transformer)架构推出的高效文生图模型,专为高速高质量生成设计。相比传统扩散模型动辄20~50步的推理过程,它仅需9步即可输出细节丰富的图像,在RTX 4090D等高显存机型上几乎秒级出图。 更关键的是,本次使用的镜像已预置全部32.88GB模型权重文件,直接缓存在系统盘中,避免了动辄数小时的下载等

By Ne0inhk

Qwen2.5-7B智能写作:营销文案自动生成实战

Qwen2.5-7B智能写作:营销文案自动生成实战 1. 引言:大模型驱动内容创作新范式 1.1 营销文案生成的行业痛点 在数字营销时代,高质量、高频率的内容输出已成为品牌竞争的核心。然而,传统文案创作面临三大挑战: * 人力成本高:专业文案撰写耗时耗力,难以满足多平台、多语种的内容需求 * 风格一致性差:不同作者或团队产出的内容调性不统一,影响品牌形象 * 响应速度慢:面对热点事件或市场变化,人工创作难以实现分钟级响应 尽管已有多种AI写作工具,但在长文本逻辑连贯性、结构化输出控制、多语言适配能力等方面仍存在明显短板。 1.2 Qwen2.5-7B的技术突破与应用价值 Qwen2.5 是最新的 Qwen 大型语言模型系列。对于 Qwen2.5,我们发布了从 0.5 到 720 亿参数的多个基础语言模型和指令调优语言模型。Qwen2.5 在 Qwen2

By Ne0inhk