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';}}

运行上面代码

用途:报告违反规则的行为

  1. 前提条件违规
  • 前提条件 = 关于输入的期望(有效函数参数)
  • 违规示例: 越界容器索引/平方根为负数
  • 宽契约函数在使用其输入值之前执行前置条件检查
    在性能关键的代码中,如果传入的参数已经知道是有效的,那么人们不想支付输入有效性检查的成本,因此通常不会使用这些方法。
  1. 未能建立/保持不变量
  • 公共成员函数无法设置有效的成员值
  • 内存不足,向量vector增长失败
  1. 后置条件违规
  • 后置条件 = 关于输出的期望值(返回值)
  • 违规=函数未能产生有效的返回值或损坏全局状态
  • 例子:
    • 构造函数失败
    • 无法返回除以零的结果

异常的优点和缺点
优点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) 的函数
  • 明确的声明另有说明

附上原文地址
如果文章对您有用,请随手点个赞,谢谢!^_^

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/40048.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot源码阅读3-启动原理

SpringBootApplication public class DistApplication {public static void main(String[] args) {// 启动入口SpringApplication.run()SpringApplication.run(DistApplication.class, args);} }1、服务构建 这里"服务"指的是SpringApplication对象&#xff0c;服务…

2024年港澳台联考考生成绩数据分析来啦

分数线 出炉 2024年的港澳台联考正式出分&#xff01;根据考生成绩&#xff0c;全国联招划档线如下&#xff1a; 一、本科批次 &#xff08;一&#xff09;普通类院校&#xff08;专业&#xff09;&#xff1a;文史类365分、理工类390分&#xff08;部分院校执行高分线&#…

持续直击WCCI 2024:金耀初教授、台湾省台北分会等获殊荣 横滨夜景美不胜收

持续直击WCCI 2024&#xff1a;金耀初教授、台湾省台北分会等获殊荣&#xff01;横滨夜景美不胜收&#xff01; 会议之眼 快讯 会议介绍 IEEE WCCI&#xff08;World Congress on Computational Intelligence&#xff09;2024&#xff0c;即2024年IEEE世界计算智能大会&…

BAS(入侵与攻击模拟)正在替代红队测试?

之前经常会被用户问到&#xff0c;漏扫、渗透和红队红的区别是啥&#xff1f; 传统的漏扫、渗透和红蓝对抗&#xff0c;可以看到工具化的漏洞不可靠&#xff0c;人工的成本就高。怎么找到一个漏洞可信度又高&#xff0c;成本又低的&#xff0c;就诞生了BAS。 抛开漏扫&#xf…

umi项目中的一些趣事

前言 出于某些安全问题&#xff0c;需要把HTML中框架注入的umi版本信息去掉&#xff0c;那要怎么搞呢~ 方案 查找官方文档&#xff0c;没发现可以去掉注入信息的方法&#xff0c;但在一番折腾后&#x1f609;终究还是解决了~ 发现 版本信息是从这里注入的~ Object.define…

解决pip安装时的“SyntaxError: invalid syntax”错误

项目场景&#xff1a; 项目中有新的成员加入时&#xff0c;第一步就是安装开发环境&#xff0c;然而往往同样的机器、同样的配置&#xff0c;我们却总能遇到各种各样不同的问题。 今天分享一个简单的操作问题。 问题描述 项目用到pandas&#xff0c;安装pandas时遇到Syntax…

Java后端每日面试题(day2)

目录 Session和Cookie的关系Cookie与Session的区别JWT 由哪些部分组成&#xff1f;如何防止 JWT 被篡改&#xff1f;JWT 的特点 Session和Cookie的关系 Session和Cookie都可以用来实现跟踪用户状态&#xff0c;而二者是关系的&#xff1a;Session的实现依赖于Cookie。 Session…

【C语言】顺序表经典算法

本文介绍的是两道顺序表经典算法题目。 移除元素 &#xff08;来源&#xff1a;LeetCode&#xff09; 题目 分析 我们很容易想到的办法是去申请一个新的数组&#xff0c;遍历原数组不等于val就把它拿到新数组里。但是题目的要求是不使用额外空间&#xff0c;所以这种方法我们…

Python面向对象编程中的继承及其应用

目录 1. 继承的基本概念 2. 继承的语法 3. 继承的应用场景 4. 使用示例&#xff1a;汽车销售系统 5. 总结 继承是面向对象编程中的一个重要概念&#xff0c;它允许我们根据已有类创建新类&#xff0c;并继承已有类的属性和方法。在本文中&#xff0c;我们将学习Python中的…

Unity3D中,AI角色Rigidbody旋转导致的动画问题

在制作一些AI角色的时候&#xff0c;可能会运用到Rigidbody组件来使AI角色拥有一些相关的物理属性&#xff0c;但是AI角色在受到一些物理碰撞或者惯性等原因&#xff0c;会发生旋转导致动画出现意料外的错误&#xff0c;比如在由动转静的时候&#xff0c;可能会发生向前翻转等一…

卷积层里的填充和步幅

一、定义 1、对于卷积&#xff0c;我们另一个超参数是核的大小&#xff0c;通常使用的卷积核是33或者55&#xff0c;很少用偶数核 2、填充是为了让输出不变或者变大&#xff0c;是为了在输入不太大&#xff0c;又能使模型足够深的情况下使用 3、填充&#xff1a;在输入周围添…

【Java学习笔记】java图形界面编程

在前面的章节中&#xff0c;我们开发运行的应用程序都没有图形界面&#xff0c;但是很多应用软件&#xff0c;如Windows下的Office办公软件、扑克牌接龙游戏软件、企业进销存ERP系统等&#xff0c;都有很漂亮的图形界面。素以需要我们开发具有图形界面的软件。 Java图形界面编程…

Megatron-DeepSpeed与Megatron-LM在reduce grad上的差异

Megatron-DeepSpeed与Megatron-LM在reduce grad上的差异 一.Megatron-DeepSpeed 实现【deepspeed/runtime/engine.py】二.ModelLink 实现【ParamAndGradBuffer】1.ParamAndGradBuffer功能介绍2.实现原理A.分配一大块内存B.获取视图C.all_reduce grad 测试DP1,TP2,PP1,MBS1,zero…

【信即是功夫】人皆有良知在心中

良知就是做人、做事的准则&#xff0c;良知就是天理&#xff1b;实实在在地自信 每个人心中都有一个圣人&#xff0c;只因自己不能真的相信&#xff0c;把这个圣人埋没了 良知在每个人心中&#xff0c;无论你如何做&#xff0c;也无法泯灭它。即使身为盗贼的人&#xff0c;他…

WebStorm配置路径别名(jsconfig.json)

项目是 ViteVueTs 新建一个 jsconfig.json文件 {"compilerOptions": {"baseUrl": ".","paths": {"/*": ["./src/*"]}},"exclude": ["node_modules", "dist"] }然后在 vite.confi…

Sping源码(九)—— Bean的初始化(非懒加载)—mergeBeanDefinitionPostProcessor

序言 前几篇文章详细介绍了Spring中实例化Bean的各种方式&#xff0c;其中包括采用FactoryBean的方式创建对象、使用反射创建对象、自定义BeanFactoryPostProcessor以及构造器方式创建对象。 创建对象 这里再来简单回顾一下对象的创建&#xff0c;不知道大家有没有这样一个疑…

day01-切片和索引

day01-切片和索引 ndarray对象的内容可以通过索引或切片来访问和修改&#xff0c;与 Python 中list 的切片操作一样。 ndarray数组可以基于0-n的下标进行索引 注意&#xff0c;数组切片并不像列表切片会重新开辟一片空间&#xff0c;而是地址引用&#xff0c;需要使用.copy()…

算法day1 两数之和 两数相加 冒泡排序 快速排序

两数之和 最简单的思维方式肯定是去凑两个数&#xff0c;两个数的和是目标值就ok。这里两遍for循环解决。 两数相加 敲了一晚上哈哈&#xff0c;结果超过int范围捏&#xff0c;难受捏。 public class Test2 {public static void main(String[] args) { // ListNode l1 …

IO多路复用学习

Linux中的I/O多路复用 相关基础概念 在学习I/O多路复用之前&#xff0c;先学习一些相关的基础概念&#xff0c;便于理解。 内核缓冲区和用户缓冲区 &#x1f62e; CPU 上会运行两种程序&#xff0c;一种是操作系统的内核程序&#xff08;也称为系统程序&#xff09;&#x…

【HarmonyOS4学习笔记】《HarmonyOS4+NEXT星河版入门到企业级实战教程》课程学习笔记(二十)

课程地址&#xff1a; 黑马程序员HarmonyOS4NEXT星河版入门到企业级实战教程&#xff0c;一套精通鸿蒙应用开发 &#xff08;本篇笔记对应课程第 30 节&#xff09; P30《29.数据持久化-用户首选项》 实现数据持久化在harmonyOS中有很多种方式&#xff0c;比较常见的是以下两…