C++初学者指南-3.自定义类型(第一部分)-异常
C++初学者指南-3.自定义类型(第一部分)-异常
文章目录
- C++初学者指南-3.自定义类型(第一部分)-异常
- 简介
- 什么是异常?
- 第一个示例
- 用途:报告违反规则的行为
- 异常的替代方案
- 标准库异常
- 处理
- 问题和保证
- 资源泄露
- 使用 RAII 避免内存泄漏!
- 析构函数:不要让异常逃脱!
- 异常保证
- 无抛出异常保证:noexcept (C++11)
简介
什么是异常?
可以在调用层次结构中向上抛出的对象。
- 抛出将控制权转移回当前函数的调用方
- 它们可以通过try…catch块捕获/处理
- 如果不处理,异常会向上传播,直到它们到达 main
- 如果main中没有处理异常,将会调用std::terminate
- std::terminate 的默认行为是中止程序

第一个示例
异常的最初动机是报告构造函数未能正确初始化对象,即未能建立所需的类不变量(构造函数没有可用于错误报告的返回类型)。
#include <stdexcept> // standard exception types
class Fraction {int numer_;int denom_;
public: explicit constexprFraction (int numerator, int denominator): numer_{numerator}, denom_{denominator}{if (denom_ == 0) throw std::invalid_argument{"denominator must not be zero"};}…
};
int main () {try { int d = 1;std::cin >> d;Fraction f {1,d}; …} catch (std::invalid_argument const& e) {// deal with / report error herestd::cerr << "error: " << e.what() << '\n';}…
}
运行上面代码
用途:报告违反规则的行为
- 前提条件违规
- 前提条件 = 关于输入的期望(有效函数参数)
- 违规示例: 越界容器索引/平方根为负数
- 宽契约函数在使用其输入值之前执行前置条件检查
在性能关键的代码中,如果传入的参数已经知道是有效的,那么人们不想支付输入有效性检查的成本,因此通常不会使用这些方法。
- 未能建立/保持不变量
- 公共成员函数无法设置有效的成员值
- 内存不足,向量vector增长失败
- 后置条件违规
- 后置条件 = 关于输出的期望值(返回值)
- 违规=函数未能产生有效的返回值或损坏全局状态
- 例子:
- 构造函数失败
- 无法返回除以零的结果
异常的优点和缺点
优点1:将错误处理代码与业务逻辑分离
优点2:错误处理集中化(在调用链的更高层)
优点3:现在,当没有抛出异常时,性能影响可以忽略不计
缺点1:但是,抛出异常 时通常会影响性能
缺点2:由于额外的有效性检查而影响性能
缺点3:容易产生资源/内存泄漏(更多见下文)
异常的替代方案
输入值无效(违反前提条件)
- 窄契约函数:在传递参数之前确保参数有效
- 使用可以排除无效值的参数类型
- 如今这是首选以获得更好的性能
未能建立/保留不变量
- 错误状态/标志
- 将对象设置为特殊的无效值/状态
无法返回有效值(违反后置条件)
- 通过单独的输出参数(引用或指针)返回错误代码
- 返回特殊的无效值
- 使用特殊的词汇类型,可以包含有效结果,也可以什么都不包含,就像C++17的std::optional或Haskell的Maybe
标准库异常
异常是 C++ 标准库使用继承的少数地方之一:
所有标准异常类型都是std::exception的子类型。
std::exception↑ logic_error| ↑ invalid_argument| ↑ domain_error| ↑ length_error| ↑ out_of_range| …↑ runtime_error↑ range_error↑ overflow_error↑ underflow_error…
try {throw std::domain_error{"Error Text"};
}
catch (std::invalid_argument const& e) {// 仅仅处理 'invalid_argument'异常…
}
// 捕捉其它所有异常
catch (std::exception const& e) {std::cout << e.what()// prints "Error Text"
}
一些标准库容器提供了宽契约函数,通过抛出异常来报告无效的输入值:
std::vector<int> v {0,1,2,3,4};
// 窄契约:不检查以获取最大性能
int a = v[6]; // 未定义行为
// 宽契约:检查是否超范围
int b = v.at(6); // throws std::out_of_range
处理
重新抛出异常
try { // potentially throwing code
}
catch (std::exception const&) { throw; // re-throw exception(s)
}
捕获所有异常
try { // potentially throwing code
}
catch (...) { // handle failure
}
集中异常处理!
- 如果同样的异常类型在许多不同的地方被抛出,可以避免代码重复。
- 对于将异常转换为错误代码很有用
void handle_init_errors () {try { throw; // re-throw! } catch (err::device_unreachable const& e) { … } catch (err::bad_connection const& e) { … } catch (err::bad_protocol const& e) { … }
}
void initialize_server (…) {try {…} catch (...) { handle_init_errors(); }
}
void initialize_clients (…) {try {…} catch (...) { handle_init_errors(); }
}
问题和保证
资源泄露
几乎任何一段代码都可能抛出异常导致对 C++ 类型和库的设计产生重大影响。
如果与以下内容一起使用,则可能是资源/内存泄漏的潜在来源
- 进行自己的内存管理的外部 C 库
- (设计不佳)不使用 RAII 进行自动资源管理的 C++ 库
- (设计不佳)在销毁时不清理资源的类型
示例:由于 C 风格的资源处理而导致的泄漏
即,两个单独的函数用于资源初始化(连接)和资源终止(断开连接)。
void add_to_database (database const& db, std::string_view filename) {DBHandle h = open_dabase_conncection(db); auto f = open_file(filename);// 如果 "open_file"抛出异常,则链接不会断开!// do work…close_database_connection(h);// ↑ 如果"open_file"抛出了异常不会执行上面代码
}
使用 RAII 避免内存泄漏!
RAII 又是什么?
- 构造函数:资源获取
- 析构函数:资源释放/终结
如果抛出异常:
- 局部作用域中的对象被销毁:被调用的析构函数
- 使用 RAII:正确释放资源
class DBConnector {DBHandle handle_;
public:explicitDBConnector (Database& db): handle_{make_database_connection(db)} {}~DBConnector () { close_database_connection(handle_); }// 使connector不能复制:DBConnector (DBConnector const&) = delete;DBConnector& operator = (DBConnector const&) = delete;
};
void add_to_database (database const& db, std::string_view filename) {DBConnector(db);auto f = open_file(filename);// 如果 'open_file' 抛出异常 ⇒ 连接关闭!// do work normally…
} // 连接关闭了!
如果你需要使用一个(比如来自C语言的)库,这个库采用独立的初始化和资源释放函数,那么就编写一个RAII包装器。
通常,如果无法控制引用的外部资源,将包装器设为不可复制(删除复制构造函数和复制赋值运算符)也是有意义的。
析构函数:不要让异常逃脱!
… 否则资源可能会泄露!
class E {
public:~E () { // throwing code ⇒ BAD!} …
};
class A {// some members:G g; F f; E e; D d; C c; B b;…
};

如果对象e析构时抛出异常的话会导致 f 和 g 对象的析构函数没有被调用。
在析构函数中: 捕获可能引发异常的代码!
class MyType {
public:~MyType () { …try {// y throwing code…} catch ( /* … */ ) {// handle exceptions…} …}
};
异常保证
如果引发异常:
不能保证
任何 C++ 代码都应该默认做出这个假设,除非它的文档另有说明:
- 操作可能会失败
- 资源可能泄露
- 可能会破坏不变性(= 成员可能包含无效值)
- 部分执行失败的操作可能会导致副作用(例如输出)
- 异常可能会向外传播
基本保正
- 不变量被保留,没有资源泄漏
- 所有成员都将包含有效值
- 执行失败操作的部分可能会导致一些副作用(例如,值可能已写入文件)
这是你最起码的目标!
强保证(提交或回滚语义)
- 操作可能会失败,但不会产生明显的副作用
- 所有成员都保留其原值
内存分配容器应该提供这一保证,即,如果在增长过程中内存分配失败,容器应保持有效和不变。
无抛出异常保证(最强)
- 保证操作成功
- 外部看不到任何异常(要么没有抛出异常,要么在内部被捕获了)
- 使用 noexcept 关键字进行记录和强制执行
在高性能代码和资源受限的设备上,首选此功能。
无抛出异常保证:noexcept (C++11)
void foo () noexcept { … }
- ‘foo’ 承诺永远不会抛出异常或让任何异常逃逸
- 如果一个异常从一个 noexcept 函数中逃逸了,程序会被终止
好好想想,你能不能遵守不抛出异常的承诺!
- noexcept是函数的接口的一部分(甚至是自C++17函数类型的一部分)
- 稍后将不抛出异常的函数更改为抛出异常的函数可能会破坏那些依赖不必处理异常的调用代码
有条件noexcept
| A noexcept( expression ) | 如果表示式为真则声明A不抛出异常 |
| A noexcept( noexcept( B ) ) | 如果B为不抛出异常则声明A也不抛出异常 |
默认情况下为 noexcept(true)
都是隐式声明的特殊成员
- 默认构造函数
- 析构函数
- 复制构造函数, 移动构造函数
- 复制赋值运算符、移动赋值运算符
- 继承的构造函数
- 用户定义的析构函数
以上这些都是默认noexcept(true)
除非
- 他们需要调用 noexcept(false) 的函数
- 明确的声明另有说明
附上原文地址
如果文章对您有用,请随手点个赞,谢谢!^_^
相关文章:
C++初学者指南-3.自定义类型(第一部分)-异常
C初学者指南-3.自定义类型(第一部分)-异常 文章目录 C初学者指南-3.自定义类型(第一部分)-异常简介什么是异常?第一个示例用途:报告违反规则的行为异常的替代方案标准库异常处理 问题和保证资源泄露使用 RAII 避免内存泄漏!析构函数:不要让异…...
学会python——用python编写一个电子时钟(python实例十七)
目录 1.认识Python 2.环境与工具 2.1 python环境 2.2 Visual Studio Code编译 3.电子时钟程序 3.1 代码构思 3.2代码实例 3.3运行结果 4.总结 1.认识Python Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python 的设计具有很强的可读性…...
elementui中@click短时间内多次触发,@click重复点击,做不允许重复点击处理
click快速点击,发生多次触发 2.代码示例: //html<el-button :loading"submitLoading" type"primary" click"submitForm">确 定</el-button>data() {return {submitLoading:false,}}//方法/** 提交按钮 */sub…...
助力游戏实现应用内运营闭环,融云游戏社交方案升级!
通信能力在所有应用场景都是必备组件,这源于社交属性带给应用的增长神话。 在游戏场景,玩家从少数核心向大众用户泛化扩展的过程,就是游戏深度融合社交能力的过程。 从单机到联机,游戏乐趣的升级 1996 年,游戏界顶流…...
守护创新之魂:源代码防泄漏的终极策略
在信息化快速发展的今天,企业的核心机密数据,尤其是源代码,成为了企业竞争力的关键所在。然而,源代码的泄露风险也随之增加,给企业的安全和发展带来了巨大威胁。在这样的背景下,SDC沙盒作为一种创新的源代码…...
Halcon 基于分水岭的目标分割
一 分水岭 1 分水岭介绍 传统的分水岭分割方法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是地质学上的拓扑地貌,图像中每一像素的灰度值表示该点的海拔高度,每一个局部极小值及其周边区域称为集水盆地&…...
PHP 面向对象编程(OOP)入门指南
面向对象编程(Object-Oriented Programming,简称OOP)是一种编程范式,通过使用对象来设计和组织代码。PHP作为一种广泛使用的服务器端脚本语言,支持面向对象编程。本文将介绍PHP面向对象编程的基本概念和用法࿰…...
Django学习第三天
python manage.py runserver 使用以上的命令启动项目 实现新建用户数据功能 views.py文件代码 from django.shortcuts import render, redirect from app01 import models# Create your views here. def depart_list(request):""" 部门列表 ""&qu…...
Vue3实现点击按钮实现文字变色
1.动态样式实现 1.1核心代码解释: class"power-station-perspective-item-text": 为这个 span 元素添加了一个 CSS 类,以便对其样式进行定义。 click"clickItem(item.id)": 这是一个 Vue 事件绑定。当用户点…...
深入理解Vue生命周期钩子函数
深入理解Vue生命周期钩子函数 Vue.js 是一款流行的前端框架,通过其强大的响应式数据绑定和组件化的开发方式,使得前端开发变得更加简单和高效。在Vue应用中,每个组件都有其生命周期,这些生命周期钩子函数允许开发者在不同阶段执行…...
Linux-gdb
目录 1.-g 生成含有debug信息的可执行文件 2.gdb开始以及gdb中的常用执行指令 3.断点的本质用法 4.快速跳出函数体 5.其他 1.-g 生成含有debug信息的可执行文件 2.gdb开始以及gdb中的常用执行指令 3.断点的本质用法 断点的本质是帮助我们缩小出问题的范围 比如,…...
Oracle分析表和索引(analyze)
分析表 analyze table tablename compute statistics; 分析索引 analyze index indexname compute statistics; 该语句生成的统计信息会更新user_tables这个视图的统计信息,分析的结果被Oracle用于基于成本的优化生成更好的查询计划 对于使用CBO(Cost-Base Optimization)很有好…...
MyBatis踩坑记录-多表关联字段相同,字段数据覆盖问题
MyBatis踩坑记录-多表关联字段相同,字段数据覆盖问题 1. 背景描述2. 实体记录3. 错误映射3.1 造成的影响 4. 解决办法4.1 修改映射文件 5. 修复后的效果5.1 返回的数据5.2 正确展示 7. end ~ 1. 背景描述 现有一下业务,单个任务下可能会有多个子任务&am…...
昇思25天学习打卡营第6天|数据变换 Transforms
学习目标:熟练掌握数据变换操作 熟悉mindspore.dataset.transforms接口 实践掌握常用变换 昇思大模型平台学习心得记录: 一、关于mindspore.dataset.transforms 1.1 变换 mindspore.dataset.transforms.Compose将多个数据增强操作组合使用。 mindspo…...
在线JSON可视化工具--改进
先前发布了JSON格式化可视化在线工具,提供图形化界面显示结构关系功能,并提供JSON快速格式化、JSON压缩、快捷复制、下载导出、对存在语法错误的地方能明确显示,而且还支持全屏,极大扩大视野区域。 在线JSON格式化可视化工具 但…...
探讨命令模式及其应用
目录 命令模式命令模式结构命令模式适用场景命令模式优缺点练手题目题目描述输入描述输出描述题解 命令模式 命令模式是一种行为设计模式, 它可将请求转换为一个包含与请求相关的所有信息的独立对象。 该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其…...
1、音视频解封装流程---解复用
对于一个视频文件(mp4格式/flv格式),audio_pkt或者video_pkt是其最基本的数据单元,即视频文件是由独立的视频编码包或者音频编码包组成的。 解复用就是从视频文件中把视频包/音频包单独读取出来保存成独立文件,那么如何得知packet是视频包还是…...
centos7升级gcc到7.3.0
1、下载gcc-7.3.0源码 wget ftp.gnu.org/gnu/gcc/gcc-7.3.0/gcc-7.3.0.tar.gz 2、解压gcc-7.3.0 tar -xvf gcc-7.3.0.tar.gz3、安装依赖 cd gcc-7.3.0 ./contrib/download_prerequisites ./contrib/download_prerequisites会下载对应的依赖包,如果下载不了的话&a…...
系统运维面试题总结(网络基础类)
系统运维面试题总结(网络基础类) 网络基础类第七层:应用层第六层:表示层第五层:会话层第四层:传输层第三层:网络层第二层:数据链路层第一层:物理层 类似面试题1、TCP/IP四…...
PO模式登录测试
项目实践 登陆项目测试 get_driver import page from selenium import webdriverclass GetDriver:driver Noneclassmethoddef get_driver(cls):if cls.driver is None:cls.driver webdriver.Edge()cls.driver.maximize_window()cls.driver.get(page.url)return cls.drivercl…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
