【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

全网最全100道C++高频经典面试题及答案解析:C++程序员面试题库分类总结

全网最全100道C++高频经典面试题及答案解析:C++程序员面试题库分类总结

前言 C++作为一门兼具高性能与灵活性的语言,持续推动着量子计算、自动驾驶、区块链、AI编译器等领域的技术革命。本题库精选100道高频面试题,涵盖从内存模型、编译器内部机制到跨学科前沿应用的深度内容,专为资深工程师、系统架构师及科研岗位设计。无论是准备顶级科技公司面试,还是探索C++在安全关键系统(如航天、医疗)与新兴领域(如脑机接口、边缘AI)的工程实践,这些题目将帮助您展现对语言本质的理解和对复杂场景的掌控力。 题库特点: 垂直深入:超越语法层面,聚焦标准演进(C++20/23)、硬件协同优化及形式化验证等高级主题。 跨领域融合:结合LLVM/MLIR编译器开发、CUDA加速、实时操作系统等场景,体现C++的系统级控制能力。 第一部分:面向对象与内存管理(1-10题) 1. 虚函数实现原理(字节跳动/腾讯) 题目:虚函数表(vtable)在C++中是如何工作的?写出示例代码说明动态多态的实现。

By Ne0inhk

3.6-Web后端基础(java操作数据库)

目录 前言 JDBC 介绍 查询数据 需求 准备工作 代码实现 代码剖析 ResultSet 预编译SQL SQL注入 SQL注入解决 性能更高 增删改数据 需求 代码实现 Mybatis 介绍 快速入门 辅助配置 配置SQL提示 配置Mybatis日志输出 JDBC VS Mybatis 数据库连接池 介绍 产品 增删改查操作 删除 新增 修改 查询 XML映射配置 XML配置文件规范 XML配置文件实现 MybatisX的使用 SpringBoot配置文件 介绍 语法 案例 前言 在前面我们学习MySQL数据库时,都是利用图形化客户端工具(如:idea、datagrip),来操作数据库的。 我们做为后端程序开发人员,

By Ne0inhk
SkyWalking - .NET / C++ / Lua 探针现状与社区支持

SkyWalking - .NET / C++ / Lua 探针现状与社区支持

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕SkyWalking这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * SkyWalking - .NET / C++ / Lua 探针现状与社区支持 🌐 * 一、SkyWalking 多语言探针架构概览 🧩 * 二、Java 探针:成熟稳定,功能最全 ☕️ * 示例:Spring Boot 应用接入 SkyWalking * Java 探针高级特性 * 三、.NET 探针现状:渐趋成熟,生产可用 🖥️ * 技术原理 * 使用方式 * 当前支持的功能 * 局限性 * 四、C++ 探针现状:SDK 形式,适合嵌入式场景 ⚙️ * cpp2sky SDK

By Ne0inhk

从“会聊天”到“会交付”:用 OpenClaw + DeepSeek 做一个可落地的 AI Agent 工程化流水线(Java/Go/Python)

从“会聊天”到“会交付”:用 OpenClaw + DeepSeek 做一个可落地的 AI Agent 工程化流水线(Java/Go/Python) 主品牌:王仕宇(JavaPub) 关键词:OpenClaw、DeepSeek、AI Agent、大模型工程化、AI Coding、面试提效 一、今天的行业信号:Agent 正在从 Demo 走向交付 过去一年,大家都在讨论“AI 会不会替代程序员”。到 2026 年,一个更务实的问题已经出现: 你的 Agent,能不能稳定、可观测、可复用地交付结果? 这背后不是模型参数竞赛,而是工程化能力竞赛: * 任务编排是否可控(Cron / Heartbeat

By Ne0inhk