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…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...
MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分: 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10+pip3.10)
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10pip3.10) 一:前言二:安装编译依赖二:安装Python3.10三:安装PIP3.10四:安装Paddlepaddle基础框架4.1…...
