C++中的异常处理方式

目录

一、异常

二、C语言中对错误的处理

三、C++中的异常处理

四、异常的抛出和捕获

五、异常的重新抛出

 六、C++标准库中的异常体系

 七、异常的规范


一、异常

        在C++中,异常是程序运行期间发生的意外或错误情况。这些情况可能会导致程序无法继续正常执行,但又不能在当前的代码位置立即处理。异常提供了一种机制,使得在错误发生时可以将控制权从当前代码的执行位置转移到异常处理代码的位置,从而进行适当的处理。

1.异常类型通常是类,可以是标准库中的异常类,也可以是自定义的异常类。异常类通常派生自 std::exception 类或其派生类。通过定义不同类型的异常类,可以让程序区分不同类型的错误情况,并采取相应的处理措施。

2.当程序在执行过程中遇到错误情况时,可以使用 throw 关键字手动抛出异常。throw 后面通常跟着一个异常对象,可以是任何类型的对象,包括基本类型、类、指针等。

3.异常捕获通过 try 和 catch 块来实现。try 块用于包裹可能抛出异常的代码块,而 catch 块用于捕获并处理在 try 块中抛出的异常。catch 块可以捕获特定类型的异常或者所有类型的异常。

4.异常处理是指在程序运行时遇到异常情况时所采取的行动。异常处理的目的是保证程序的稳定性和可靠性,使得程序能够在出错情况下进行适当的恢复或终止。

5.当一个函数中抛出了异常,但该函数没有处理这个异常时,异常将会被传递给调用该函数的上层函数,直到找到相应的异常处理代码为止。如果异常一直没有被处理,最终将导致程序的终止。

        通过异常机制,C++ 提供了一种结构化的方式来处理程序中的错误情况,从而提高了程序的健壮性和可维护性。

二、C语言中对错误的处理

C语言中的处理机制:

1. 终止程序,如 assert ,缺陷:用户难以接受。如发生内存错误,除 0 错误时就会终止程序。
2. 返回错误码 ,缺陷:需要程序员自己去查找对应的错误。如系统的很多库的接口函数都是通过把错误码放到errno 中,表示错误

这两个方法存在不足之处

1. 终止程序,如assert

        当程序遇到错误时,直接终止程序可能会给用户带来不好的体验,特别是对于用户交互型的程序而言。这种突然的终止也不利于程序的稳定性和可维护性。

2. 返回错误码

        需要程序员在每次调用可能出错的函数后手动检查返回值,并查找对应的错误码,这增加了代码的复杂性和出错的可能性。此外,如果程序员忘记检查错误码,可能会导致错误被忽略或未处理,进而引发更严重的问题。同时,错误码通常是整数类型,可能不够精确地描述错误的性质和上下文,导致错误处理不够灵活。

三、C++中的异常处理

在C++中,通常使用三个关键字来处理:

throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
catch: 在您想要处理问题的地方,通过异常处理程序捕获异常 . catch 关键字用于捕获异常,可以有多个catch 进行捕获。
try: try 块中的代码标识将被激活的特定异常 , 它后面通常跟着一个或多个 catch 块。

我们来看一下下面的例子(除0异常)

#define  _CRT_SECURE_NO_WARNINGS
#include <iostream>
int main() {int dividend = 10;int divisor = 0;float result;try {if (divisor == 0) {throw "除数不能为零";}result = static_cast<float>(dividend) / divisor;std::cout << "以下代码不会被执行" << std::endl;std::cout << "结果: " << result << std::endl;}catch (const char* msg) {std::cerr << "异常: " << msg << std::endl;}return 0;
}

可以看到一旦throw后,会直接跳转到对应的catch,后面的代码不会被执行。

四、异常的抛出和捕获

1. 异常是通过 抛出对象而引发 的,该 对象的类型 决定了应该激活哪个 catch 的处理代码。
// 抛出对象决定匹配的 catch 块
void throwException(int value) {if (value < 0) {throw "遇到负值!";}else {throw 42;}
}int main() {try {throwException(-1);}catch (const char* msg) {std::cout << "捕获到带有消息的异常: " << msg << std::endl;}catch (int num) {std::cout << "捕获到带有值的异常: " << num << std::endl;}return 0;
}

运行结果

2. 选中的处理代码 是调用链中 与该对象类型匹配且离抛出异常位置最近 的那一个。
#include <iostream>// 匹配最近的 catch 块
void tryCatchChain(int option) {try {if (option == 1) {throw "第一个";}else {throw "第二个";}}catch (const char* msg) {std::cout << "在外部 catch 块中捕获到异常: " << msg << std::endl;// throw; // 重新抛出异常}
}int main() {try {tryCatchChain(2);}catch (const char* msg) {std::cout << "在主 catch 块中捕获到异常: " << msg << std::endl;}return 0;
}

运行结果可以看到最近的是外部捕获:

3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch 以后销毁。(这里的处理类似于函数的传值返回)
#include <iostream>
// 异常对象的拷贝和销毁
class MyException {
public:MyException() {std::cout << "异常对象已创建" << std::endl;}~MyException() {std::cout << "异常对象已销毁" << std::endl;}
};
void throwException() {throw MyException();
}
int main() {try {throwException();}catch (MyException& e) {std::cout << "捕获到异常" << std::endl;}return 0;
}

运行结果:

4. catch(...)可以捕获任意类型的异常,问题是不知道异常错误是什么。
#include <iostream>// catch(...) 的通配符
void throwException() {throw "出现了问题!";
}int main() {try {throwException();}catch (...) {std::cout << "捕获到异常" << std::endl;}return 0;
}

运行结果:

5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配, 可以抛出的派生类对象, 使用基类捕获。
#include <iostream>// 使用基类捕获派生类对象
class BaseException {
public:virtual void what() const {std::cout << "基类异常" << std::endl;}
};class DerivedException : public BaseException {
public:void what() const override {std::cout << "派生类异常" << std::endl;}
};void throwException() {throw DerivedException();
}int main() {try {throwException();}catch (const BaseException& e) {std::cout << "捕获到 ";e.what();}return 0;
}

运行结果:

这个例子就发展到自定义异常体系

实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理,因为一个项目中如果大家随意抛异常,那么外层的调用者基本就没办法玩了,所以实际中都会定义一套继承的规范体系。这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了

五、异常的重新抛出

有可能单个的 catch 不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用
链函数来处理, catch 则可以通过重新抛出将异常传递给更上层的函数进行处理。
#include<iostream>
using namespace std;
double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0){throw "Division by zero condition!";}return (double)a / (double)b;
}
void Func()
{// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再// 重新抛出去。int* array = new int[10];try {int len, time;cin >> len >> time;cout << Division(len, time) << endl;}catch (...){cout << "delete []" << array << endl;delete[] array;throw;  }// ...cout << "delete []" << array << endl;delete[] array;
}
int main()
{try{Func();}catch (const char* errmsg){cout << errmsg << endl;}return 0;
}

Division 函数负责进行整数除法,但在除数为零时抛出异常 "Division by zero condition!"

Func 函数中,我们使用 new 分配了一个整数数组,然后在 try 块中调用 Division 函数,如果出现除以零的情况,会抛出异常。

在 catch 块中,我们捕获了所有类型的异常 catch (...)在这里,我们先输出一条消息表示我们正在释放动态分配的数组,然后使用 delete[] 释放数组的内存。

最后,在 catch 块中使用 throw 重新抛出异常,这样异常会继续向外传递给上层调用者处理。

在 main 函数中,我们捕获并处理 const char* 类型的异常消息,然后输出错误消息。

catch 块会捕获异常并进行处理,比如在例子中,释放动态分配的数组。如果没有重新抛出,程序会继续执行 catch 块后的代码,而不是中断执行。这可能导致程序继续执行其他代码,导致不可预期的行为或错误,比如释放两次空间。

异常的重新抛出这种方式确保了在出现异常时,动态分配的资源得到正确释放,同时异常也被传递给上层处理。

异常安全问题:

构造函数完成对象的构造和初始化 最好不要 在构造函数中抛出异常,否则 可能导致对象不 完整或没有完全初始化
析构函数主要完成资源的清理 最好不要 在析构函数内抛出异常,否则 可能导致资源泄漏 ( 内存泄漏、句柄未关闭等)
C++ 中异常经常会导致资源泄漏的问题,比如在 new delete 中抛出了异常,导致内存泄漏,在lock unlock 之间抛出了异常导致死锁, C++ 经常使用 RAII 来解决以上问题。
使用RAII来申请空间,就算直接跳到catch出了作用域也会自动调用析构来释放。

 六、C++标准库中的异常体系

        在 C++ 中,标准异常类都是以父子类层次结构组织的,它们的定义在 <stdexcept> 头文件中。这个层次结构的根是 std::exception 类,它是所有标准异常类的基类。其他的异常类都直接或间接地继承自 std::exception,形成了一个继承体系。这样的设计使得异常处理更加方便,可以根据需要捕获特定类型的异常,也可以捕获更通用的异常。

下面是一些常见的标准异常类及其关系:

  • std::exception:所有标准异常类的基类。
    • std::bad_alloc:内存分配失败时抛出的异常。
    • std::bad_cast:在动态类型转换中出现问题时抛出的异常。
    • std::bad_typeid:当 typeid 运算符应用于空指针时抛出的异常。
    • std::logic_error:所有逻辑错误异常的基类。
      • std::invalid_argument:当函数参数无效时抛出的异常。
      • std::domain_error:当函数参数超出有效域时抛出的异常。
      • std::length_error:当试图创建超出长度限制的对象时抛出的异常。
      • std::out_of_range:当使用超出有效范围的值时抛出的异常。
    • std::runtime_error:所有运行时错误异常的基类。
      • std::range_error:当执行超出范围的结果时抛出的异常。
      • std::overflow_error:当执行算术运算超出范围时抛出的异常。
      • std::underflow_error:当执行算术运算下溢时抛出的异常。

 七、异常的规范

        异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的 后面接throw(类型),列出这个函数可能抛掷的所有异常类型。

// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();
// C++11 中新增的noexcept,表示不会抛异常
thread() noexcept;
thread (thread&& x) noexcept;

如果使用noexpect仍然抛出异常,编译会告警:

但是有的编译器就算加了也不一定会告警,所以使用的时候要注意。

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

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

相关文章

二维视觉尺寸测量简单流程

代码示例&#xff1a;opencv实战---物体尺寸测量_opencv尺寸测量精度-CSDN博客 灰度化 简化图像处理&#xff1a;灰度图像只包含亮度信息&#xff0c;不包含颜色信息&#xff0c;因此数据量比彩色图像小&#xff0c;处理起来更加简单和快速。这对于需要实时处理大量图像数据的场…

XTuner笔记

为什么要微调&#xff1a; 1. 模型不具备一些私人定制的知识 2。模型回答问题的套路你不满意。 对应衍生出来两种概念 增量预训练微调&#xff1a; 使用场景&#xff1a;让基座模型学习到一些新知识&#xff0c;如某个垂类领域的常识训练数据&#xff1a;文章、书籍、代码等…

Mac电脑安装打开APP显示问题已损坏 问题解决

当MAC电脑安装完软件打开时&#xff0c;显示文件已损坏&#xff0c;无法打开。搜了很多教程终于找到解决方案&#xff0c;记录下方便以后再用。 我的mac电脑是intel芯片的&#xff0c;如果你遇到这个问题&#xff0c;可以参考我的这个方案。 1.首先当打开软件后出现 “xx软件已…

Python 框架安全:SSTI 模板注入漏洞测试.

什么是 SSTI 模板注入 SSTI (Server-Side Template Injection) 是一种Web应用程序安全漏洞&#xff0c;它发生在应用程序使用模板引擎渲染用户输入时。当应用程序将用户输入直接插入到模板中而不进行充分的过滤和验证时&#xff0c;就可能导致SSTI漏洞。攻击者可以利用这个漏洞…

Selenium 保存会话信息避免重复登录实战!

前言 • 在一些实际开发场景中&#xff0c;我们在使用 Selenium 做自动化测试时需要保留用户的会话信息&#xff0c;从而避免重复登录&#xff0c;今天这篇文章就带大家实战如何使用 Selenium 保存会话信息。 版本 • Python 3.x 整体思路 • 当我们打开页面时&#xff0c;…

Colab/PyTorch - 002 Pre Trained Models for Image Classification

Colab/PyTorch - 002 Pre Trained Models for Image Classification 1. 源由2. 图像分类的预训练模型3. 示例 - AlexNet/ResNet1013.1 模型推断过程3.2 使用TorchVision加载预训练网络3.3 使用AlexNet进行图像分类3.3.1 Step1&#xff1a;加载预训练模型3.3.2 Step2&#xff1a…

1.分布式-理论

目录 一、什么是分布式系统 二、CAP理论 1.一致性Consisency 2.可用性(Availability) 3.分区容错性(Partition tolrance) 三、BASE理论 1.Basically Available(基本可用) 2.Soft state&#xff08;软状态&#xff09; 3.Eventually consistent&#xff08;最终一致性&a…

【JavaSE】/*初识Java*/

目录 一、了解 Java 语言 二、Java 语言的重要性 2.1 使用程度 2.2 工作领域 三、Java 语言的特性 四、Java 的基础语法 五、可能遇到的错误 六、第一个 java 程序代码解析 七、Java 注释 八、Java 标识符 九、Java 关键字 一、了解 Java 语言 Java 是由 Sun Micr…

15. 三数之和(双指针+去重优化)

文章目录 前言一、题目描述二、代码原理1.暴力解法2.双指针优化 三.代码编写总结 前言 在本篇文章中&#xff0c;我们将会讲到leetcode中15. 三数之和&#xff0c;我们将会用到双指针的方式解决这道问题&#xff0c;同时注意掌握算法原理的去重操作。 一、题目描述 给你一个…

PHP高级教程

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;PHP &#x1f4da;参考教程&#xff1a;菜鸟\编程网❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、PHP 多维数组 二、PHP date&#xff08;&#…

免费获取!图论+灰色系统+混合优化算法程序代码!

前言 算法&#xff08;Algorithm&#xff09;是指解题方案的准确而完整的描述&#xff0c;是一系列解决问题的清晰指令&#xff0c;算法代表着用系统的方法描述解决问题的策略机制。算法优化是指对算法的有关性能进行优化&#xff0c;如时间复杂度、空间复杂度、正确性、健壮性…

Java | Leetcode Java题解之第79题单词搜索

题目&#xff1a; 题解&#xff1a; class Solution {public boolean exist(char[][] board, String word) {char[] words word.toCharArray();for(int i 0; i < board.length; i) {for(int j 0; j < board[0].length; j) {if (dfs(board, words, i, j, 0)) return t…

Linux·基本指令

从本节开始将新开一个关于Linux操作系统的板块&#xff0c;其实Linux也没什么太神秘的&#xff0c;就是一个操作系统(OS)嘛&#xff0c;跟Windows操作系统是一个概念&#xff0c;只不过Windows中的大部分操作都是用光标点击来进行人机交互&#xff0c;但是Linux是通过输入命令行…

fmt::arg的使用

来自&#xff1a; https://hackingcpp.com/cpp/libs/fmt.html #include <fmt/format.h> // literalsint main () { int i 2; double d 4.567; fmt::print("{x} {x}\n", fmt::arg("x",d)); fmt::print("{pi} * {0}\n", i, fmt::arg(&…

今天遇到一个GPT解决不了的问题

问题描述 你好&#xff0c;postman的一个post请求&#xff0c;编辑器里面放了一个很长的json数据&#xff0c;报Tokenization is skipped for long lines for performance reasons. This can be configured via editor.maxTokenizationLineLength.&#xff0c;但是同样的数据&a…

住宅ip与数据中心ip代理的区别是什么

代理通常意味着“替代”。它是用户设备和目标服务器之间的中介&#xff0c;允许在不同的IP地址下上网。代理ip根据来源分类可分住宅ip与数据中心ip&#xff0c;二者之间区别是什么呢&#xff1f; 住宅ip是由互联网服务提供商(ISP)提供给家庭的IP地址。出于这个原因&#xff0c…

灾备建设中虚拟机备份自定义数据块大小应用

灾备建设中&#xff0c;传输备份数据时&#xff0c;自定义数据块大小可以帮助优化数据传输和存储效率。 确定数据块大小&#xff0c;首先&#xff0c;需要确定合适的数据块大小。这可以根据备份数据量和网络带宽来决定。通常情况下&#xff0c;较小的数据块可以更好地适应网络…

Linux各目录及每个目录的详细介绍

目录 /bin 存放二进制可执行文件(ls,cat,mkdir等)&#xff0c;常用命令一般都在这里。 /etc 存放系统管理和配置文件 /home 存放所有用户文件的根目录&#xff0c;是用户主目录的基点&#xff0c;比如用户user的主目录就是/home/user&#xff0c;可以用~user表示 /us…

华为开源自研AI框架昇思MindSpore应用案例:在ResNet-50网络上应用二阶优化实践

常见的优化算法可分为一阶优化算法和二阶优化算法。经典的一阶优化算法如SGD等&#xff0c;计算量小、计算速度快&#xff0c;但是收敛的速度慢&#xff0c;所需的迭代次数多。而二阶优化算法使用目标函数的二阶导数来加速收敛&#xff0c;能更快地收敛到模型最优值&#xff0c…

三星硬盘格式化后怎么恢复数据

在数字化时代&#xff0c;硬盘作为数据存储的核心部件&#xff0c;承载着我们的重要文件、照片、视频等资料。然而&#xff0c;不慎的格式化操作可能使我们失去宝贵的数据。面对这样的困境&#xff0c;许多用户可能会感到无助和焦虑。本文旨在为三星硬盘用户提供格式化后的数据…