【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

【Java 开发日记】我们来说说 ThreadLocal 的原理,使用场景及内存泄漏问题

【Java 开发日记】我们来说说 ThreadLocal 的原理,使用场景及内存泄漏问题

目录 一、核心原理 1. 数据存储结构 2. 关键设计 二、源码分析 1. set() 方法流程 2. get() 方法流程 三、使用场景 1. 典型应用场景 2. 使用建议 四、内存泄漏问题 1. 泄漏原理 2. 解决方案对比 3. 最佳实践 五、注意事项 六、替代方案 七、调试技巧 面试回答 1. 首先,它的核心原理是什么? 2. 其次,它的典型使用场景有哪些? 3. 最后,关于它的内存泄漏问题 一、核心原理 1. 数据存储结构 // 每个

By Ne0inhk
OpenClaw 最新保姆级飞书对接指南教程 搭建属于你的 AI 助手

OpenClaw 最新保姆级飞书对接指南教程 搭建属于你的 AI 助手

OpenClaw 最新保姆级飞书对接指南教程 搭建属于你的 AI 助手 OpenClaw 是一款开源的本地 AI 助手,本篇 OpenClaw 安装教程将手把手教你在 Linux 系统下部署最新版 OpenClaw,并完成飞书机器人对接。OpenClaw 支持在你自己的服务器上运行,通过飞书、WhatsApp、Telegram 等聊天工具交互。与云端 SaaS 服务不同,OpenClaw 让你完全掌控数据隐私,可以执行系统命令、浏览网页、管理文件,甚至编写代码——是你的专属开源 AI 助手。 注意:本教程在 Linux 系统下进行 OpenClaw 是什么? OpenClaw(原名 Clawdbot,后更名为 Moltbot,现正式命名为 OpenClaw)是一个运行在你本地环境的高权限 AI 智能体。

By Ne0inhk

AI股票分析师daily_stock_analysis实测:3步完成私有化金融分析

AI股票分析师daily_stock_analysis实测:3步完成私有化金融分析 1. 为什么你需要一个“不联网”的股票分析工具? 你有没有过这样的经历:想快速了解一只股票的基本面,却要打开多个网页——财经新闻、股吧讨论、券商研报、交易所公告……信息杂乱,真假难辨,还可能被广告和营销内容干扰。更关键的是,当你输入敏感的自选股或内部研究代码时,是否担心数据被上传到云端?是否在意分析过程是否完全可控? 这正是 AI股票分析师daily_stock_analysis 镜像诞生的出发点:它不调用任何外部API,不连接互联网获取实时行情,也不依赖第三方服务。整个分析流程——从模型加载、提示词执行到报告生成——全部在你的本地设备上完成。你输入的股票代码(哪怕是MY-COMPANY这样的虚构代号),不会离开你的机器半步。 这不是一个“假装专业”的玩具。它用真实的大模型能力,配合严谨的角色设定和结构化输出约束,把复杂的金融分析逻辑压缩成三个清晰段落:近期表现、潜在风险、未来展望。没有图表,没有K线图,但有逻辑、有判断、

By Ne0inhk
2026年1月16日- 白嫖Claude Opus 4.5!Kiro + AIClient-2-API 让你免费用上顶级AI

2026年1月16日- 白嫖Claude Opus 4.5!Kiro + AIClient-2-API 让你免费用上顶级AI

前言 在AI辅助开发工具快速发展的今天,各大厂商纷纷推出自己的AI编程助手。好家伙,继GitHub Copilot、Cursor、Claude Code之后,AWS也按捺不住了,在2025年7月正式推出了自家的AI IDE——Kiro。这款工具不仅支持Claude Sonnet 4、Claude Opus 4.5等顶级大模型,而且新用户注册就送550积分,相当于白嫖数百次高质量AI对话,这对于想要体验顶级AI编程能力的小伙伴来说简直是福音。 但问题来了,Kiro目前只能在其IDE内部使用,如果我们想在其他工具比如Cherry Studio、Claude Code中使用这些免费额度怎么办?这就需要借助一个神器——AIClient-2-API。这个开源项目可以将Kiro等AI客户端的能力转换为标准的OpenAI API格式,让我们能够在任何支持OpenAI API的第三方工具中使用Kiro的免费额度,实现真正的"白嫖"顶级大模型。 最近两天我解锁了 Kiro 搭配 AIClient-2-API 的 “邪修玩法”,今天就手把手带大家实操:从注册 Kiro 账号、

By Ne0inhk