异常规范与自定义异常类的设计 📅 发布时间:2026/7/12 12:43:26 👁️ 浏览次数: 异常规范与自定义异常类的设计大家好 上一篇我们聊了 C 异常处理的基础——try-catch-throw 的基本用法学会了如何捕获和处理简单异常。但在实际开发中仅用基础用法远远不够比如多个函数抛出不同异常时如何清晰约束抛出类型系统自带的异常比如字符串、int 错误码无法携带足够的错误信息该怎么办今天我们就来解决这两个问题聊聊异常规范和自定义异常类的设计让我们的异常处理更规范、更灵活适配复杂项目的开发需求。话不多说咱们直奔主题全程搭配可直接运行的示例代码新手也能轻松跟上一、先搞懂什么是异常规范异常规范简单来说就是对函数可能抛出的异常类型进行明确声明告诉调用者“这个函数只会抛出这些类型的异常其他类型的异常不会从这里抛出”。为什么需要异常规范举个例子如果一个函数没有任何异常声明调用者无法提前预知它会抛出什么异常只能用 catch (…) 万能捕获既不规范也不利于排查错误而有了异常规范调用者可以精准捕获对应类型的异常代码的可读性和可维护性会大大提升。1. C 中的异常规范写法两种方式C 中异常规范有两种常用写法一种是 C98 引入的 throw 列表目前已被弃用但仍有老代码在使用另一种是 C11 引入的 noexcept 关键字推荐使用更简洁、更高效。1C98 异常规范throw 列表弃用了解即可语法格式在函数声明/定义的末尾加上 throw(异常类型1, 异常类型2, …)表示该函数只会抛出括号内列出的异常类型。#includeiostream#includestringusingnamespacestd;// 异常规范该函数只会抛出 int 或 string 类型的异常voidfunc()throw(int,string){intflag1;if(flag1){throw100;// 允许抛出 int 类型}elseif(flag2){throwstring(函数执行失败);// 允许抛出 string 类型}// 不能抛出其他类型比如 char*// throw 错误; // 编译可能报警告运行时可能触发 terminate()}intmain(){try{func();}catch(interrCode){cout捕获到 int 异常errCodeendl;}catch(string errMsg){cout捕获到 string 异常errMsgendl;}return0;}补充说明如果函数末尾写 throw()表示该函数不会抛出任何异常这种写法的缺点编译器检查不严格比如上面抛出 char* 类型有些编译器仅报警告不会报错且运行时开销较大所以 C11 后被弃用不推荐在新项目中使用。2C11 推荐noexcept 关键字重点掌握noexcept 是 C11 引入的用来替代 throw 列表语法更简洁编译器检查更严格运行时开销更小是目前主流的异常规范方式。核心用法分两种noexcept表示该函数绝对不会抛出任何异常noexcept(条件)表示当条件为 true 时函数不会抛出异常条件为 false 时可能抛出异常较少用。#includeiostream#includestringusingnamespacestd;// 异常规范该函数绝对不会抛出任何异常推荐写法voidfunc1()noexcept{coutfunc1不会抛出异常endl;// 如果强行抛出异常编译器会直接调用 terminate()程序崩溃// throw 100; // 运行时崩溃提示 terminate called after throwing an instance of int}// 异常规范该函数可能抛出异常不写 noexcept 等价于 noexcept(false)voidfunc2(){throwstring(func2抛出 string 异常);}intmain(){try{func1();func2();}catch(string errMsg){cout捕获到异常errMsgendl;}return0;}关键注意点如果一个函数声明为 noexcept但内部强行抛出异常程序会直接崩溃调用 terminate()不会进入 catch 块如果函数没有任何异常规范不写 throw 列表也不写 noexcept等价于 noexcept(false)表示“可能抛出任何类型的异常”建议明确不会抛出异常的函数一定要加 noexcept比如工具函数、简单的getter/setter可以提升程序运行效率可能抛出异常的函数无需刻意声明默认 noexcept(false)。二、核心重点自定义异常类的设计上一篇我们用 int、string 作为异常值抛出但在实际开发中这些类型的异常有明显的缺点携带的信息有限比如抛出 int 错误码调用者需要记住每个错误码对应的含义维护成本高无法区分异常来源多个函数都抛出 string 类型异常无法快速判断异常是来自哪个函数、哪个模块扩展性差无法根据需求添加额外的异常信息比如异常发生的行号、时间、模块名。此时自定义异常类就派上用场了。我们可以通过类的封装让异常携带更丰富的信息区分不同来源的异常还能实现异常的继承和扩展适配复杂项目的需求。1. 自定义异常类的设计原则必看设计自定义异常类时遵循以下3个原则能让异常处理更规范、更灵活继承自 C 标准异常类std::exception标准异常类提供了基础的异常接口比如 what() 方法用来返回异常描述继承它可以让我们的自定义异常和系统异常兼容方便统一处理提供 what() 方法重写 std::exception 的 what() 方法返回具体的异常描述信息比如“文件打开失败路径xxx”支持异常信息的自定义通过构造函数传入异常相关信息比如错误码、模块名、描述让异常携带足够的调试信息。2. 基础示例自定义异常类单异常类先从最简单的自定义异常类开始继承 std::exception重写 what() 方法支持传入异常描述和错误码。#includeiostream#includestring#includeexception// 必须包含std::exception 所在头文件usingnamespacestd;// 自定义异常类继承自 std::exceptionclassMyException:publicexception{private:interrCode;// 错误码string errMsg;// 异常描述string errModule;// 异常所在模块可选提升实用性public:// 构造函数初始化异常信息支持自定义错误码、模块、描述MyException(intcode,conststringmodule,conststringmsg):errCode(code),errModule(module),errMsg(msg){}// 重写 what() 方法返回异常描述必须是 const char* 类型virtualconstchar*what()constnoexceptoverride{// 拼接异常信息模块 错误码 描述staticstring fullMsg;// 静态变量避免返回局部变量地址fullMsg[errModule] 错误码to_string(errCode)描述errMsg;returnfullMsg.c_str();// 转换为 const char* 返回}// 可选提供获取错误码、模块的接口方便后续处理intgetErrCode()constnoexcept{returnerrCode;}stringgetErrModule()constnoexcept{returnerrModule;}};// 测试函数抛出自定义异常voidopenFile(conststringfilePath){// 模拟文件打开失败的场景if(filePath.empty()){// 抛出自定义异常错误码1001模块FileOperate描述文件路径为空throwMyException(1001,FileOperate,文件路径为空无法打开文件);}cout文件打开成功filePathendl;}intmain(){try{// 调用可能抛出自定义异常的函数openFile();// 传入空路径触发异常}// 捕获自定义异常匹配 MyException 类型catch(constMyExceptione){cout捕获到自定义异常e.what()endl;cout异常模块e.getErrModule()endl;cout错误码e.getErrCode()endl;// 可以根据错误码做不同的处理if(e.getErrCode()1001){cout建议请传入有效的文件路径endl;}}// 兜底捕获防止其他未预料到的异常catch(...){cout捕获到未知异常endl;}return0;}运行结果捕获到自定义异常[FileOperate] 错误码1001描述文件路径为空无法打开文件 异常模块FileOperate 错误码1001 建议请传入有效的文件路径关键点解析必须包含 头文件否则无法继承 std::exceptionwhat() 方法必须是 const 修饰且 noexceptC11 后推荐返回值是 const char*用静态变量拼接异常信息避免返回局部变量的地址局部变量生命周期结束后地址会失效导致乱码捕获自定义异常时建议用 const 引用const MyException e避免拷贝提升效率。3. 进阶示例自定义异常类的继承多异常场景在复杂项目中不同模块可能会抛出不同类型的异常比如文件操作异常、网络异常、数据库异常此时可以设计一个“基类异常”再让各个模块的异常类继承它实现异常的分层处理。比如基类 Exception → 子类 FileException文件异常、NetworkException网络异常这样调用者可以精准捕获某个模块的异常也可以捕获基类异常实现统一处理。#includeiostream#includestring#includeexceptionusingnamespacestd;// 异常基类继承自 std::exception所有自定义异常的父类classBaseException:publicexception{private:interrCode;string errMsg;string errModule;public:BaseException(intcode,conststringmodule,conststringmsg):errCode(code),errModule(module),errMsg(msg){}virtualconstchar*what()constnoexceptoverride{staticstring fullMsg;fullMsg[errModule] 错误码to_string(errCode)描述errMsg;returnfullMsg.c_str();}intgetErrCode()constnoexcept{returnerrCode;}stringgetErrModule()constnoexcept{returnerrModule;}};// 子类1文件异常继承自基类异常classFileException:publicBaseException{public:// 构造函数调用父类构造函数固定模块为FileModuleFileException(intcode,conststringmsg):BaseException(code,FileModule,msg){}};// 子类2网络异常继承自基类异常classNetworkException:publicBaseException{public:// 构造函数调用父类构造函数固定模块为NetworkModuleNetworkException(intcode,conststringmsg):BaseException(code,NetworkModule,msg){}};// 测试函数1文件操作抛出 FileExceptionvoidreadFile(conststringfilePath){if(filePath.find(.txt)string::npos){// 抛出文件异常错误码2001描述不是txt文件throwFileException(2001,文件格式错误仅支持.txt文件);}cout读取文件成功filePathendl;}// 测试函数2网络连接抛出 NetworkExceptionvoidconnectNetwork(conststringip){if(ip127.0.0.1){// 抛出网络异常错误码3001描述本地IP无法连接throwNetworkException(3001,本地IP127.0.0.1无法建立远程连接);}cout网络连接成功ipendl;}intmain(){try{readFile(test.docx);// 触发文件异常connectNetwork(127.0.0.1);// 触发网络异常不会执行因为上面已经抛出异常}// 捕获文件异常子类异常优先匹配catch(constFileExceptione){cout捕获到文件异常e.what()endl;}// 捕获网络异常子类异常catch(constNetworkExceptione){cout捕获到网络异常e.what()endl;}// 捕获基类异常如果子类异常未被单独捕获会被基类捕获实现统一处理catch(constBaseExceptione){cout捕获到基础异常e.what()endl;}catch(...){cout捕获到未知异常endl;}return0;}运行结果捕获到文件异常[FileModule] 错误码2001描述文件格式错误仅支持.txt文件进阶要点异常子类继承基类后无需重写 what() 方法除非需要自定义子类的异常格式直接复用父类的实现即可catch 块的顺序很重要子类异常必须放在基类异常前面如果先捕获 BaseException子类异常会被基类捕获子类的 catch 块永远不会执行这种分层设计的优势可以单独处理某个模块的异常比如文件异常做重试操作也可以通过捕获基类异常统一处理所有自定义异常比如统一记录异常日志。三、常见避坑点重点提醒自定义异常类和异常规范很容易踩坑这部分一定要记好避免写出无效的异常处理代码自定义异常类必须继承 std::exception且重写 what() 方法否则无法和系统异常兼容也无法正常捕获what() 方法的返回值必须是 const char*且不能返回局部变量的地址建议用静态变量或堆内存堆内存需注意释放避免内存泄漏noexcept 不要滥用可能抛出异常的函数不要声明为 noexcept否则强行抛出异常会导致程序崩溃catch 块顺序子类异常在前基类异常在后具体类型异常在前万能捕获在后捕获异常时优先用 const 引用const 异常类 e避免拷贝异常对象提升效率同时避免切片问题子类异常被基类对象捕获时丢失子类特有信息。四、总结与实践建议今天我们掌握了两个核心知识点异常规范和自定义异常类的设计总结一下异常规范推荐用 C11 的 noexcept明确不会抛出异常的函数加 noexcept提升效率自定义异常类继承 std::exception重写 what() 方法携带足够的异常信息错误码、模块、描述复杂项目建议用“基类子类”的分层设计核心目标让异常处理更规范、更易调试、更易维护避免滥用万能捕获让错误处理更精准。实践建议把今天的示例代码复制到编译器中运行亲手修改异常信息、错误码感受异常的捕获和处理流程尝试自己设计一个自定义异常子类比如 DatabaseException 数据库异常添加特有属性比如数据库连接地址在实际项目中给每个模块设计对应的异常类统一异常规范方便后续排查错误和维护。
信息论与编码篇---DMS不等长编码 如果说等长编码定理告诉我们"压缩的底线在哪里",那这个定理就是告诉我们"如何通过巧妙的设计,实际触碰到那条底线"。它是所有现代无损压缩算法(如ZIP、PNG图片压缩)的理论源头。1. 为什么需要不等长编码&… 2026/5/17 4:58:20
[精品]基于微信小程序的汽车车险销售系统 UniApp 文章目录项目介绍项目效果图开发核心技术介绍:SpringBoot和Vue 介绍系统测试详细视频演示源码获取项目介绍 随着互联网技术的迅猛发展,传统的车险销售模式已经逐渐跟不上时代的步伐。用户对保险产品的需求越来越多元化和个性化,传统的销售方… 2026/7/9 0:10:32
信息论与编码篇---等长编码 1. 什么是等长编码?想象一下,你要给班上的每个同学发一个编号,方便点名。等长编码:就是规定每个同学的编号长度都一样。比如:规定编号必须是两位数字。那么,张三就是 01,李四就是 02,… 2026/7/12 15:08:50
Stable Diffusion巨构建筑生成:黄金梦想之地提示词详解 这次我们来看一个名为"巨构-黄金梦想之地"的AI绘画提示词组合,这个提示词组合在Stable Diffusion社区中引起了广泛关注。它最大的特点是能够生成具有史诗感和建筑奇观的图像,特别适合创作科幻、奇幻风格的巨型建筑场景。 这个提示词组合的核心… 2026/7/13 9:44:34
从零搭建upload-labs靶场:21关文件上传漏洞实战与防御指南 1. 项目概述与核心价值如果你是一名Web安全爱好者、渗透测试初学者,或者是一名想深入理解文件上传漏洞原理的开发者,那么亲手搭建一个upload-labs靶场,绝对是性价比最高的学习路径之一。这个靶场由国内安全研究员c0ny1开发,它不像… 2026/7/13 9:42:32
C++类型转换进阶:从static_cast到dynamic_cast的实战指南 1. 项目概述:为什么C类型转换是进阶路上的“分水岭”?刚接触C时,我们都是从int a (int)3.14;这种C风格的类型转换开始的。它简单直接,就像一把万能钥匙,似乎什么锁都能开。但随着项目规模扩大,代码量激增&… 2026/7/13 9:40:31
面试题:大模型的 DPO 和 PPO 的区别是什么? 先说结论:DPO 和 PPO 不是“新旧替代关系” DPO 和 PPO 都是在 SFT 之后继续做偏好对齐,让模型不只“能回答”,还更倾向于给出人类认为有帮助、真实、安全、符合场景的回答。它们最大的差别,不是名字里有没有“强化学习”… 2026/7/13 9:40:31
Claude 3.7 vs GPT-4o真实项目实战:LogSift日志告警工具开发复盘 1. 项目概述:不是跑分,是真刀真枪写一个能上线的工具 “Claude 3.7 发布第一天,我就拿它和 GPT-4o 写了个真实项目”——这句话里藏着三个关键信息点: 时效性、对抗性、落地性 。它不是在模型发布后第七天补个测评,也… 2026/7/13 9:40:31
消解原理与子句集转换:从谓词逻辑到5步标准化实战解析 谓词逻辑到子句集:5步标准化实战与Python实现从理论到实践的智能推理基础在自动定理证明和知识表示领域,将谓词逻辑公式转化为标准子句集是一项基础而关键的技能。这项技术构成了现代人工智能系统中逻辑推理的基石,支撑着从专家系统到复杂决策… 2026/7/13 9:38:30
HS2-HF Patch终极指南:如何用3步解决Honey Select 2的70+个痛点 HS2-HF Patch终极指南:如何用3步解决Honey Select 2的70个痛点 【免费下载链接】HS2-HF_Patch Automatically translate, uncensor and update HoneySelect2! 项目地址: https://gitcode.com/gh_mirrors/hs/HS2-HF_Patch 你是否曾经在Honey Select 2中遇到过… 2026/7/13 0:01:19
语音转文字工具AsrTools:让音频整理变得简单高效 语音转文字工具AsrTools:让音频整理变得简单高效 【免费下载链接】AsrTools ✨ AsrTools: Smart Voice-to-Text Tool | Efficient Batch Processing | User-Friendly Interface | No GPU Required | Supports SRT/TXT Output | Turn your audio into accurate text … 2026/7/13 0:03:19
基于深度学习的蘑菇或花卉或动漫人物或中草药货水果蔬菜等识别系统31(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_ 基于深度学习的蘑菇或花卉或动漫人物或中草药货水果蔬菜等识别系统31(设计源文件万字报告讲解)(支持资料、图片参考_相关定制)_ 独家界面!不会重复,此项目属于本人原创,若有雷同,均是盗卖,各位买… 2026/7/13 0:05:20
Git reset 与 revert 深度对比:5个关键差异与 3 种典型应用场景 Git Reset 与 Revert 深度对比:5个关键差异与3种典型应用场景在团队协作开发中,代码版本管理如同行走钢丝——一步失误可能导致整个项目陷入混乱。作为Git进阶用户,你是否曾在深夜面对错误的提交束手无策?是否在强制推送后收到同事… 2026/7/13 8:31:55
GitHub 学生包申请避坑:5个常见失败原因与开发者工具调试方案 GitHub 学生包申请技术排障指南:5个高频失败场景与开发者工具实战方案第一次尝试申请GitHub学生包时,我盯着屏幕上那个不断转圈的加载动画整整15分钟,最终只等来了一行冰冷的错误提示。这可能是许多开发者共同的经历——明明按照教程操作&… 2026/7/13 9:31:08
冒烟测试用例设计规范:5%-10%覆盖率下的3类核心场景与执行标准 冒烟测试用例设计的黄金法则:5%-10%覆盖率下的精准筛选策略在快节奏的敏捷开发环境中,冒烟测试作为质量保障的第一道防线,其重要性不言而喻。当测试资源有限而时间紧迫时,如何从海量测试用例中精准筛选出那关键的5%-10%࿰… 2026/7/13 2:34:55