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://xiahunao.cn/news/3018105.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

[译文] 恶意代码分析:1.您记事本中的内容是什么?受感染的文本编辑器notepad++

这是作者新开的一个专栏&#xff0c;主要翻译国外知名安全厂商的技术报告和安全技术&#xff0c;了解它们的前沿技术&#xff0c;学习它们威胁溯源和恶意代码分析的方法&#xff0c;希望对您有所帮助。当然&#xff0c;由于作者英语有限&#xff0c;会借助LLM进行校验和润色&am…

IOT-9608I-L ADC端口的使用(连续采样ADC值)

目录 概述 1 硬件介绍 1.1 认识硬件 1.2 引脚信号定义 2 软件功能实现 2.1 查看iio:device0下的接口信息 2.2 实现连续采样ADC 2.2.1 功能描述 2.2.2 代码实现 2.2.3 详细代码 3 测试 概述 本文主要讲述IOT-9608I-L ADC端口的使用方便&#xff0c;其内容包括板卡上的…

自动化机器学习——贝叶斯优化

自动化机器学习——贝叶斯优化 贝叶斯优化是一种通过贝叶斯公式推断出目标函数的后验概率分布&#xff0c;从而在优化过程中不断地利用已有信息来寻找最优解的方法。在贝叶斯优化中&#xff0c;有两个关键步骤&#xff1a;统一建模和获得函数的优化。 1. 统一建模 在贝叶斯优…

Windows端之Python3.9及以上高版本工程打包得到的exe逆向工程解包得到pyc文件进而得到py文件的流程实现

参考来自 【python逆向 pyc反编译】python逆向全版本通杀_python反编译pyc-CSDN博客https://blog.csdn.net/zjjcxy_long/article/details/127346296Pyinstaller打包的exe之一键反编译py脚本与防反编译_pyinstaller防止反编译-CSDN博客https://blog.csdn.net/as604049322/artic…

嵌入式RTOS面试题目

用过哪些嵌入式操作系统&#xff1f;使⽤RTOS和裸机代码开发有什么区别&#xff08;优缺点&#xff09;&#xff1f; 之前的⼀个项⽬是采⽤裸机代码开发的&#xff0c;写起来还⾏&#xff0c;通过状态机来管理业务逻辑和各种外设。 但是随着外设的增加&#xff0c;任务之间的…

【数学建模】天然肠衣搭配问题

2011高教社杯全国大学生数学建模竞赛D题 天然肠衣&#xff08;以下简称肠衣&#xff09;制作加工是我国的一个传统产业&#xff0c;出口量占世界首位。肠衣经过清洗整理后被分割成长度不等的小段&#xff08;原料&#xff09;&#xff0c;进入组装工序。传统的生产方式依靠人工…

【深度学习】实验1 波士顿房价预测

波士顿房价预测 代码 import numpy as np import matplotlib.pyplot as pltdef load_data():# 1.从文件导入数据datafile D:\Python\PythonProject\sklearn\housing.datadata np.fromfile(datafile, sep )# 每条数据包括14项&#xff0c;其中前面13项是影响因素&#xff0c…

iOS xib布局

1.多次启动发现启动图和截屏的图片不一致,设置launch storyboard 不能到顶部 https://blog.csdn.net/u011960171/article/details/104053696/ 2.multipiler是比例&#xff0c;需要控制顺序1.视图&#xff0c;2父视图&#xff0c;选择宽度比例&#xff0c;默认是1 3.Aspect R…

CSS-伪类选择器

结构伪类选择器 作用&#xff1a;根据元素的结构关系查找元素 分类&#xff1a; 选择器说明元素名:first-child查找第一个元素元素名:last-child查找最后一个元素元素名:nth-child(N)查找第N名元素 <!DOCTYPE html> <html lang"en"> <head><me…

《Beginning C++20 From Novice to Professional》第十章 Function Templates

C Template 基础篇&#xff08;一&#xff09;&#xff1a;函数模板_函数模板的定义及使用-CSDN博客 这篇博客提到模板是泛型编程的基础&#xff0c;把类型也当做参数&#xff0c;这样使得静态类型语言对类型的处理更强大&#xff0c;提高了代码的可重用性&#xff0c;目标和软…

Java 框架安全:Spring 漏洞序列.(CVE-2022-22965)

什么叫 Spring 框架. Spring 框架是一个用于构建企业级应用程序的开源框架。它提供了一种全面的编程和配置模型&#xff0c;可以简化应用程序的开发过程。Spring 框架的核心特性包括依赖注入&#xff08;Dependency Injection&#xff09;、面向切面编程&#xff08;Aspect-Or…

【漏洞复现】CData API Server 路径遍历漏洞(CVE-2024-31849)

0x01 产品简介 CData API Server是CData公司的一个强大的数据连接平台&#xff0c;旨在帮助企业轻松地访问、整合和分析各种数据源。 0x02 漏洞概述 CData API Server 23.4.8846之前版本存在安全漏洞&#xff0c;该漏洞源于存在路径遍历漏洞。攻击者可利用该漏洞获得对应用程…

深度剖析Comate智能产品:科技巧思,实用至上

文章目录 Comate智能编码助手介绍Comate应用场景Comate语言与IDE支持 Comate安装步骤Comate智能编码使用体验代码推荐智能推荐生成单测注释解释注释生成智能问答 Comate实战演练总结 Comate智能编码助手介绍 市面上现在有很多智能代码助手&#xff0c;当时互联网头部大厂百度也…

Selenium——获取元素和操纵元素的方法

1、获取元素的方法 1、通过id获取 element wd.find_element(By.ID,"id")2、通过classname获取 elements wd.find_elements_by_class_name("plant") for element in elements:print(element.text)3、通过tagname获取元素 elements wd.find_elements_…

Node.js版本管理工具nvm的安装和使用

介绍 nvm全称 Node Version Manager 顾名思义它是用来管理 node 版本的工具&#xff0c;方便切换不同版本的Node.js。 使用 nvm的使用非常简单&#xff0c;跟npm的使用方法类似 下载 首先下载nvm&#xff0c;下载地址https://github.com/coreybutler/nvm-windows/releases…

目前最便宜的VPS多少钱一个月?

目前最便宜的VPS一个月的价格在5美元左右&#xff0c;换算成人民币约为35元。 VPS服务器的配置、性能、所在地区都是影响其价格的因素&#xff0c;价格与性能呈正相关&#xff0c;也有的廉价VPS的服务商会提供性能低的配置&#xff0c;让用户可以进行简单的网站托管或开发环境…

Elasticsearch FSCrawler 一个bug及解决方案

1、FSCrawler Bug 发现过程及描述 书接上一回&#xff0c;在使用 Elasticsearch FSCrawler 实现文档知识库检索的时候。 发现基于本地磁盘文件轮询导入 Elasticsearch 都没有问题。 但是&#xff0c;借助其 REST API 接口上传文件的时候&#xff0c;发现其字段 filesize 字段没…

安装Nox夜神模拟器关闭了HyperV后Docker运行不了怎么办?

1.背景 为了模拟真机&#xff0c;尝试安装了Nox夜神模拟器&#xff0c; 安装过程要求关闭Hyper-V。当时只是在程序安装卸载中关闭了系统服务。以为到时勾选上就好了。操作路径&#xff1a;控制面板\所有控制面板项\程序和功能\启用或关闭Windows功能\Hyper-V。 后来卸载掉了夜神…

C++ list 介绍

&#x1f308;一、认识list这个模版 ist是一个模版&#xff0c;需要结合一个具体的数据类型作为模版参数&#xff0c; 即list < T > <T> <T>&#xff0c;才能成为一个类类型。list是双向循环链表&#xff0c;是序列容器&#xff0c;允许在序列中的任何位置进…

智启算力平台基本操作

智启算力平台 智启算力平台路径搭载数据集搭载镜像配置 智启算力平台 开发文档 帮助文档 - OpenI - 启智AI开源社区 路径搭载 OpenIOSSG/promote: 启智AI协作平台首页推荐组织及推荐项目申请。 - notice/Other_notes/SDKGetPath.md at master - promote - OpenI - 启智AI开…