C++异常处理系列从基础到通天(一篇即毕业系列)
文章目录异常处理基本概念定义关键字用法代码块代码块C标准异常类层次结构标准异常类层次结构示例自定义异常类示例noexcept验证函数是否 noexcept什么时候使用 noexcept异常安全性异常跨函数传播示例Copy-and-Swap 惯用法与异常安全基本模式异常安全流程拆解关键要点首先effective c中条款第29提到请记住1. 异常安全函数 Exception-safe functions即使发生异常也不会泄露资源或允许任何数据类型败坏。这样的函数区分为三种可能的保证基本型 强烈型 不抛异常型2. “强烈保证” 往往能够以 copy-and-swap 实现出来 但 “强烈保证” 并非对所有函数都可实现或具备现实意义3. 函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的 “异常安全保证”中的最弱者我需要告诉你本篇甚至大部分的异常处理都围绕基本型异常。强烈型异常基本就用copy-and-swap见文章最后即可不抛异常很简单就是noexcept关键字。详细见下文注意别小看以上内容如果你要真正成为一名程序员一定需要先成为一位异常处理大师本系列所有内容都如同本篇一样优秀喔欢迎点赞关注异常处理异常处理是一项重要的特性它允许程序在运行时遇到错误条件时能够优雅地恢复或终止执行而不是简单地崩溃。C通过try、catch和throw三个关键字来实现异常处理机制。本文会详细介绍。基本概念定义异常是指在程序执行过程中发生的、不符合程序正常流程的事件。在C中异常通常是由于某些错误条件触发的如除以零、数组越界、内存分配失败等。关键字try用于标记可能会抛出异常的代码块称为保护代码。throw当检测到异常条件时使用throw关键字抛出一个异常。throw后面可以跟任意表达式它的类型决定了抛出的异常类型。catch用于捕获并处理异常。catch块紧跟在try块之后并指定了要捕获的异常类型。用法下面是一个简单的例子演示了如何使用try、throw和catch来处理除以零的异常代码块#includeiostreamusingnamespacestd;doubledivision(inta,intb){if(b0){throwDivision by zero!;// 抛出异常}return(a/b);}intmain(){intx50;inty0;doublez0;try{zdivision(x,y);// 可能抛出异常的代码coutzendl;}catch(constchar*msg){// 捕获并处理异常cerrmsgendl;}return0;}在这个例子中如果y为零division函数将抛出一个字符串异常该异常在main函数的catch块中被捕获并处理。一个try块可以跟随多个catch块来捕获多种不同类型的异常代码块#includeiostream#includestdexcept// 包含标准异常类usingnamespacestd;// 为了演示方便项目中不建议这样使用voidtestFunction(){throwruntime_error(Runtime error occurred!);}intmain(){try{testFunction();// 可能抛出异常的函数}catch(constlogic_errore){coutCaught a logic_error: e.what()endl;}catch(construntime_errore){coutCaught a runtime_error: e.what()endl;}catch(...){// 捕获所有其他类型的异常coutCaught an unknown exceptionendl;}return0;}testFunction抛出了一个runtime_error异常该异常在第二个catch块中被捕获并处理。最后一个catch块使用省略号…来捕获所有其他类型的异常。C标准异常类C标准库提供了一系列预定义的异常类这些类都继承自std::exception基类。使用标准异常类可以使代码更加清晰。层次结构如图标准异常类层次结构std::exception所有标准异常的基类。std::bad_alloc内存分配失败时抛出。std::bad_cast动态类型转换失败时抛出。std::bad_typeid使用 typeid 运算符失败时抛出。std::bad_exception在函数声明中使用了异常规格但抛出了未列出的异常时抛出 (C11已弃用)。std::logic_error逻辑错误异常基类包括:std::domain_error数学域错误如sqrt(-1)。std::invalid_argument无效参数错误。std::length_error超出允许长度的错误。std::out_of_range范围错误如访问vector的非法索引。std::runtime_error运行时错误异常基类包括:std::overflow_error上溢错误。std::range_error范围错误与 std::out_of_range 不同用于其他情况。std::underflow_error下溢错误。示例#includeiostream#includestdexceptusingnamespacestd;voidtestLogicError(){throwinvalid_argument(Invalid argument error!);}voidtestRuntimeError(){throwrange_error(range error!);}intmain(){try{testLogicError();// 抛出逻辑错误异常}catch(constlogic_errore){coutCaught a logic_error: e.what()endl;}try{testRuntimeError();// 抛出运行时错误异常}catch(construntime_errore){coutCaught a runtime_error: e.what()endl;}return0;}testLogicError 函数抛出了一个 invalid_argument 异常而 testRuntimeError 函数抛出了一个 range_error 异常。这两个异常分别在对应的 catch 块中被捕获并处理。自定义异常类虽然C标准库提供了丰富的异常类但在某些情况下开发者可能需要定义自己的异常类。我们可以通过继承 std::exception 基类并重载 what 方法来实现。示例#includeiostream#includeexception#includestringusingnamespacestd;classMyException:publicexception{public:MyException(conststringmessage):message_(message){}virtualconstchar*what()constnoexceptoverride{returnmessage_.c_str();}private:string message_;};voidtestCustomException(){throwMyException(Custom exception occurred!);}intmain(){try{testCustomException();// 抛出自定义异常}catch(constMyExceptione){coutCaught a MyException: e.what()endl;}catch(constexceptione){coutCaught an unknown exception: e.what()endl;}return0;}MyException 类继承自 std::exception 并重载了 what 方法。testCustomException 函数抛出了一个 MyException 异常该异常在 main 函数的 catch 块中被捕获并处理。注意这里还添加了一个捕获所有其他 std::exception 子类的 catch 块确保能够捕获所有未知异常。noexcept从C11开始推荐使用noexcept关键字来声明函数不抛出任何异常:voidfunc()noexcept;// 声明函数不抛出任何异常如果func函数在执行过程中抛出了异常程序会直接终止。noexcept关键字还可以用于提高性能因为编译器可以优化不抛出异常的函数调用。如果你看过 gcc 源码你会发现基本上通篇都是noexcept。noexcept会告诉编译器它修饰的函数不会产生异常exception这有利于编译器做更多的优化。C 的异常处理是在运行时检测的而不是在编译时检测为了运行时检测编译器应该会做些额外的操作如果能够通过noexcept明确的告诉编译器这个函数不会抛出异常编译器应该会做一些优化。验证函数是否 noexceptnoexcept还可以当作运算符它可以传入参数来验证某个函数是否是noexcept:voidmay_throw();voidno_throw()noexcept;intmain(){noexcept(may_throw());// falsenoexcept(no_throw());// true}什么时候使用 noexcept移动构造函数移动赋值函数建议使用noexcept修饰因为搭配标准库使用时noexcept作用巨大它可以优先移动而非拷贝推荐阅读 move_if_noexcept。而析构函数默认就是noexcept的不需要显式指定noexcept。在明确确认某个函数不会产生 exception 时可以使用noexcept。推荐看看这几个noexcept相关的文档:https://www.cnblogs.com/sword03/p/10020344.htmlhttps://en.cppreference.com/w/cpp/language/noexcept_spechttps://en.cppreference.com/w/cpp/language/noexcepthttps://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Re-noexcepthttps://stackoverflow.com/questions/10787766/when-should-i-really-use-noexcepthttps://www.heise.de/blog/C-Core-Guidelines-Der-noexcept-Spezifier-und-Operator-4121657.html异常安全性异常安全性是指程序在遇到异常时仍然能够保持正确的状态不会出现资源泄露或者数据不一致等问题。因为C允许程序在执行过程中抛出异常这可能导致程序的控制流发生变化如果资源管理不当就可能出现资源泄露或程序崩溃等问题。异常跨函数传播示例有些场景下通过异常处理错误会更方便特别是跨函数传播的时候。场景描述图像处理流水线假设我们有一个图像处理系统分为三个独立编译的模块可能是不同团队开发的库底层库:ImageDecoder第三方库负责解码图像文件。中间层:ImageProcessor内部库负责图像增强。应用层:MainApp主程序处理用户请求。当底层库解码失败时异常会穿过中间层直达应用层处理。简易代码:// 底层库, 解码classImageDecoder{staticvoiddecode(){throwImageException(decode error);}};// 中间层, 图像处理voidImageProcess(){ImageDecoder::decode();}// 应用层intmain(){try{ImageProcess();}catch(ImageException e){xxx}catch(...){}return0;}Copy-and-Swap 惯用法与异常安全核心思想以拷贝构造 非抛出交换实现赋值操作利用 RAII 自动管理资源确保赋值要么完全成功要么原对象不变强异常安全保证。基本模式classBuffer{int*data_;size_t size_;// 私有辅助非抛出交换friendvoidswap(Buffera,Bufferb)noexcept{usingstd::swap;swap(a.data_,b.data_);swap(a.size_,b.size_);}public:Buffer(size_t n):data_(newint[n]),size_(n){}// 拷贝构造可能抛异常new但这是新对象没问题Buffer(constBufferother):data_(newint[other.size_]),size_(other.size_){std::copy(other.data_,other.data_other.size_,data_);}// 移动构造Buffer(Bufferother)noexcept:data_(other.data_),size_(other.size_){other.data_nullptr;other.size_0;}// 析构函数~Buffer(){delete[]data_;}// 拷贝赋值copy-and-swap 的核心Bufferoperator(constBufferother){// 1. 拷贝构造一个临时对象可能抛异常// 如果抛异常this 原封不动 → 强异常安全Buffertmp(other);// 2. 非抛出交换swap(*this,tmp);// 3. tmp 析构释放旧资源return*this;}// 移动赋值也可以复用 copy-and-swapBufferoperator(Bufferother)noexcept{swap(*this,other);return*this;}};异常安全流程拆解步骤操作若抛异常为什么安全1Buffer tmp(other)拷贝构造抛出 →tmp未构造完毕this不变拷贝在独立对象上进行2swap(*this, tmp)交换指针noexcept不会抛只用noexcept操作交换资源所有权3tmp析构析构不抛tmp现在持有旧资源自动释放关键要点swap必须noexcept— 这是整个惯用法的前提。如果swap可能抛就失去了强异常保证。参数传值 vs 传引用— 可以用传值代替传 const 引用 临时构造编译器可以优化// 简写传值方式copy-and-swap 的另一种写法Bufferoperator(Buffer other)noexcept{swap(*this,other);return*this;}这种写法同时支持拷贝赋值和移动赋值传右值时调用移动构造。自赋值安全— copy-and-swap 天然处理自赋值因为拷贝构造了独立副本交换后再释放旧资源。性能权衡每次赋值都分配新内存对于频繁赋值的场景可能不划算。如果性能敏感可以用copy-on-write或先判断资源是否足够复用。

相关新闻

15兆瓦海上风电仿真:IEA-15-240-RWT完整指南与实战应用

15兆瓦海上风电仿真:IEA-15-240-RWT完整指南与实战应用

15兆瓦海上风电仿真:IEA-15-240-RWT完整指南与实战应用 【免费下载链接】IEA-15-240-RWT 15MW reference wind turbine repository developed in conjunction with IEA Wind 项目地址: https://gitcode.com/gh_mirrors/ie/IEA-15-240-RWT 你是否想要快速掌握…

2026/7/21 17:15:30 阅读更多 →
长晶科技IC产品线解析与功率器件应用指南

长晶科技IC产品线解析与功率器件应用指南

1. 长晶科技IC产品线全景概览长晶科技作为国内半导体行业的重要参与者,其IC产品线覆盖了功率器件、模拟芯片、MCU等多个领域。我拆解过他们近三年发布的全部产品手册,发现其产品布局呈现明显的金字塔结构:基础型产品占比约60%,中端…

2026/7/21 18:24:59 阅读更多 →
F280015x ADC同步采样与高级应用实战:从SOC配置到系统级诊断

F280015x ADC同步采样与高级应用实战:从SOC配置到系统级诊断

1. 项目概述:深入理解F280015x的ADC同步采样与高级应用在电机控制、数字电源或者任何需要高精度实时反馈的嵌入式系统中,模数转换器(ADC)的性能往往是决定系统上限的关键瓶颈。我们常常面临这样的挑战:如何精确地同时捕…

2026/7/21 18:25:02 阅读更多 →

最新新闻

【UniApp小程序知识点总结】何时使用官方类型及如何快速查找

【UniApp小程序知识点总结】何时使用官方类型及如何快速查找

目录 一、 什么是官方 TS 类型? 二、 官方文档与资源 三、 何时使用官方 TS 类型? 四、 如何知道是用官方的哪个 TS 类型? 五、 总结 六、 建议 在 Uniapp 或微信小程序原生开发中使用 TypeScript 时,很多开发者会遇到类型定义的困扰。是应该自己手写 interface,还是…

2026/7/21 20:30:07 阅读更多 →
如何在ESP32项目中实现ML307 4G模块的稳定移动网络连接

如何在ESP32项目中实现ML307 4G模块的稳定移动网络连接

如何在ESP32项目中实现ML307 4G模块的稳定移动网络连接 【免费下载链接】xiaozhi-esp32 An MCP-based chatbot | 一个基于MCP的聊天机器人 项目地址: https://gitcode.com/GitHub_Trending/xia/xiaozhi-esp32 还在为你的物联网设备寻找可靠的移动网络解决方案吗&#xf…

2026/7/21 20:30:07 阅读更多 →
如何选择最适合你的PortaPack硬件版本:从需求分析到实战部署的完整指南

如何选择最适合你的PortaPack硬件版本:从需求分析到实战部署的完整指南

如何选择最适合你的PortaPack硬件版本:从需求分析到实战部署的完整指南 【免费下载链接】mayhem-firmware The firmware for the HackRFPortaPack H1/H2/H4/H4M 项目地址: https://gitcode.com/gh_mirrors/ma/mayhem-firmware PortaPack Mayhem固件作为HackR…

2026/7/21 20:30:07 阅读更多 →
Garmin Connect 数据备份高效解决方案

Garmin Connect 数据备份高效解决方案

Garmin Connect 数据备份高效解决方案 【免费下载链接】garminexport [DEPRECATED] Garmin Connect activity exporter and backup tool 项目地址: https://gitcode.com/gh_mirrors/ga/garminexport Garminexport 是一个专为 Garmin Connect 用户设计的智能数据备份工具…

2026/7/21 20:30:07 阅读更多 →
VC++股票分析系统:从数据采集到K线绘制的完整实现

VC++股票分析系统:从数据采集到K线绘制的完整实现

1. 项目概述:从零构建一个VC股票分析系统最近在整理硬盘,翻出来一个十多年前用VC6.0写的股票分析系统源码。现在看,代码风格有点“复古”,但整个架构和实现思路,对于想用C做金融数据分析、或者想深入理解MFC框架下如何…

2026/7/21 20:30:07 阅读更多 →
SMS-Tools终极指南:音乐分析合成完整解决方案揭秘

SMS-Tools终极指南:音乐分析合成完整解决方案揭秘

SMS-Tools终极指南:音乐分析合成完整解决方案揭秘 【免费下载链接】sms-tools Sound analysis/synthesis tools for music applications 项目地址: https://gitcode.com/gh_mirrors/sms/sms-tools 当声音遇见科学,当艺术邂逅算法,音乐…

2026/7/21 20:29:06 阅读更多 →

日新闻

Octane Render与C4D汉化版安装与优化指南

Octane Render与C4D汉化版安装与优化指南

1. Octane Render与C4D的黄金组合:为什么选择这个方案?在三维创作领域,渲染器的选择往往决定了作品的最终呈现质量和工作效率。作为Cinema 4D(C4D)用户,Octane Render的GPU加速特性与实时预览功能&#xff…

2026/7/21 0:00:19 阅读更多 →
GPMC接口设计:异步/同步模式与多路复用配置实战

GPMC接口设计:异步/同步模式与多路复用配置实战

1. GPMC接口设计:从硬件连接到软件配置的全局视角在嵌入式系统开发中,尤其是基于TI Sitara系列如AM263x这类高性能微控制器的项目里,外部存储器的扩展几乎是绕不开的一环。无论是存放大量非易失性代码的NOR Flash,还是作为高速数据…

2026/7/21 0:00:19 阅读更多 →
UE5 GAS框架下RPG被动技能系统:从核心原理到实战实现

UE5 GAS框架下RPG被动技能系统:从核心原理到实战实现

1. 项目概述:UE5 GAS RPG被动技能的核心价值在UE5里用GAS(Gameplay Ability System)做RPG游戏,主动技能像是你手里的武器,按一下打一下,逻辑直接,反馈也快。但被动技能,它更像是你身…

2026/7/21 0:00:19 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/21 8:48:31 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/21 5:34:47 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/21 8:25:39 阅读更多 →

月新闻