【C++】异常之道,行者无疆:解锁 C++ 的异常捕获哲学

news2025/2/24 8:50:24

文章目录

  • C语言处理错误
  • C++异常
  • 异常的抛出与捕获
    • 基本语法
    • `catch` 的匹配原则
    • 函数调用链中的匹配原则
    • 异常的重新抛出
  • 异常安全
  • 异常规范
  • C++标准库异常

C语言处理错误

  • 终止程序:利用 assert() 断言去终止程序,当 ()的表达结果为 false 时会终止程序。
  • 返回错误码:手动查找对应的错误,系统的接口函数将作错误码放到 errno 中表示错误。

C语言中的 strerror 将参数对应 errno 的错误信息的字符串返回。errno 是一个全局变量,当使用标准库的函数发生错误时,就会将对应的的错误码放到 errno 中,每个错误码对应着不同的错误信息,strerror 就可以将错误码对应的字符串返回。
以下为错误码 0~10 对应的信息:

#include <iostream>
#include <errno.h>
using namespace std;

int main()
{
	for (size_t i = 0; i <= 10; ++i)
		cout << i << ":" << strerror(i) << endl;
	return 0;
}

在这里插入图片描述

C++异常

C++的异常处理是一种应对程序运行时错误的机制,允许在程序中独立开发的部分在运行时就出现的错误进行通信并作出相应的处理,使得将问题的检测与解决问题的过程分开,程序的一部分检测问题的出现,然乎将解决任务传递给程序的一部分。

总的来说,异常能够提供一种结构化的方法来捕获和处理错误,从而提高代码的健壮性和可维护性。

异常的抛出与捕获

基本语法

C++的常处理依赖于三个关键字:

  • try:用于定义可能抛出异常的代码块。
  • throw:用于抛出异常对象。
  • catch:用于捕获异常,并定义处理异常的逻辑。

使用实例

void testException(int x)
{
	if (x == 0)
		throw "Division by zero error!"; //抛出 const char* 类型的异常

	cout << "x:" << x << endl;
}

int main()
{
	try {
		testException(0);
	}
	catch (const char* e)
	{
		cout << "Caught an exception: " << e << endl;
	}

	return 0;
}

catch 的匹配原则

1.异常的类型

C++支持抛出任意类型的对象作为异常,例如:

  • 基本类型:如 intcharfloat
  • 标准库类型:如 string
  • 自定义类型:通过类和结构体定义的类型。

示例:

throw 42;                // 抛出整数
throw std::string("Error"); // 抛出字符串
throw std::runtime_error("Runtime error"); // 抛出标准库异常类

在这里插入图片描述

2.标准异常类

C++标准库还提供了一组异常类,位于 exception 头文件中。常见的标准异常类包括:

  • exception:所有标准异常的基类。
  • runtime_error:运行时错误。
  • logic_error:逻辑错误,如非法参数、越界访问等。
  • 其他派生类:如 out_of_rangeinvalid_argument

使用标准异常类:

int main()
{
	try {
		throw runtime_error("Runtime exception occurred!");
	}
	catch (const exception& e)
	{
		cout << "Caught standard exception: " << e.what() << endl;
	}

	return 0;
}

3.匹配原则

  • 异常抛出的对象的类型决定了应该匹配哪个 catch 的处理代码,该过程不会有隐式类型转换
void testException(int x)
{
	if (x == 0)
		throw "Division by zero error!"; //抛出 const char* 类型的异常

	cout << "x:" << x << endl;
}

int main()
{
	try {
		testException(0);
	}
	catch(const string& e)	//不会有隐式类型转换,不会匹配string的catch
	{
		cout << "Caught an exception(string): " << e << endl;
	}
	catch (const char* e)
	{
		cout << "Caught an exception: " << e << endl;
	}

	return 0;
}

在这里插入图片描述

  • 如果有多个类型匹配的 catch 的版本,则会去调用离抛出异常位置最近catch
void testException(int x)
{
	if (x == 0)
		throw "Division by zero error!"; //抛出 const char* 类型的异常

	cout << "x:" << x << endl;
}

void test1()
{
	try {
		testException(0);
	}
	catch (const char* e)	
	{
		cout << "(most near) Caught an exception: " << e << endl;
	}
}

int main()
{
	try {
		test1();
	}
	catch (const char* e)
	{
		cout << "Caught an exception: " << e << endl;
	}
	
	return 0;
}

在这里插入图片描述

  • throw 抛出异常对象后,会生成异常对象的拷贝,因为抛出的异常对象可能是临时对象。这个拷贝的临时异常对象会在被 catch 后销毁(类似于函数的传值返回)

  • catch(...) 可以捕获任意类型的异常对象,但不知道具体的异常错误。

int main()
{
	try {
		throw runtime_error("Runtime exception occurred!");
	}
	catch (...)
	{
		cout << "Unkonwn exception!" << endl;
	}

	return 0;
}

在这里插入图片描述

  • 实际上,抛出的异常对象类型实际上也不需要完全匹配,比如:可以抛出派生类对象用基类捕获。

函数调用链中的匹配原则

  1. 当异常在 try 代码块中 throw 抛出时,它会沿函数调用链向上传播,直到找到匹配的 catch 代码块而且此过程中,throw 后面的代码不再执行。

  2. 匹配到对应异常对象的类型的 catch 代码块后,沿着函数调用链销毁沿途的对象。

  3. 如果最后在 main 函数中没有匹配的 catch,程序会调用 terminate 函数,通常导致程序终止。

  4. 一般为了避免这种情况,需要用 catch(...) 防止异常没有捕获导致程序崩溃。
    在这里插入图片描述
    具体例子

void testException(int x)
{
	if (x == 0)
		throw "Division by zero error!"; //抛出 const char* 类型的异常

	cout << "x:" << x << endl;
}

void test1()
{
	try {
		testException(0);
	}
	catch (const string& e)	
	{
		cout << "(string) Caught an exception: " << e << endl;
	}
}

int main()
{
	try {
		test1();
	}
	catch (const char* e)
	{
		cout << "Caught an exception: " << e << endl;
	}
	catch (...)		//如果抛出的异常未经处理,程序会被terminate终止
	{
		cout << "Unkonwn exception!" << endl;
	}
	
	return 0;
}

在这里插入图片描述

异常的重新抛出

catch 代码块中,可以通过 throw 关键字将异常重新抛出,供更高层次的代码处理。

void testException(int x)
{
	if (x == 0)
		throw "Division by zero error!"; //抛出 const char* 类型的异常

	cout << "x:" << x << endl;
}

void test1()
{
	try {
		testException(0);
	}
	catch (const char* e)
	{
		cout << "(Inner catch) Caught an exception: " << e << endl;
		//将捕获到的异常重新抛出
		throw;
	}
}

int main()
{
	try {
		test1();
	}
	catch (const char* e)
	{
		cout << "(Outer catch) Caught an exception: " << e << endl;
	}
	catch (...)		//如果抛出的异常未经处理,程序会被terminate终止
	{
		cout << "Unkonwn exception!" << endl;
	}
	
	return 0;
}

在这里插入图片描述

异常安全

  • 构造函数最好不要抛异常,因为可能导致对象不完整初始化甚至完全没有初始化。
  • 析构函数也最好不要抛异常,否则可能造成资源泄漏。
void  testException(int x)
{
	int* array = new int[10];
	if (x == 0)
		throw "Division by zero error!"; //抛出 const char* 类型的异常

	cout << "x:" << x << endl;
	delete[] array;
}

int main()
{
	try
	{
		testException(0);
	}
	catch (const char* e)
	{
		cout << "Caught an exception: " << e << endl;
	}
	
	return 0;
}

这里由于 throw 抛异常提前退出了 testException 函数导致执行 delete[] array 导致了资源泄漏,这是非常危险的,一定要避免。

异常规范

  • 在函数的后面接 throw(type) ,表示这个函数可能抛出的所有的异常类型。
  • 函数后面接 throw() 表示函数不抛异常,在C++11中新增关键字 noexcept ,表示该函数不抛异常。
  • noexcept 会影响异常的捕获,确认函数不会加才使用。
// 这里表示这个函数会抛出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;

C++标准库异常

C++ 标准库中提供了一组异常类,用于支持异常处理机制。这些异常类都继承自 exception,提供了统一的接口以便捕获和处理各种类型的异常。

核心异常类
exception 是描述标准库异常的基类,其接口为 virtual const char* what() const noexcept ,返回异常的描述信息。

  • bad_alloc :表示内存分配失败的异常,通常由 new 操作符抛出。
  • bad_cast :表示动态类型转换(dynamic_cast)失败的异常。
  • bad_typeid :在对空指针调用 typeid 时抛出。
  • bad_exception :如果异常对象在 throw 时不匹配声明的异常类型,可能会抛出此异常。

逻辑错误异常
logic_error 是一个逻辑错误的基类,表示程序中的逻辑问题,通常在运行时能够检测到。

  • domain_error :表示函数参数超出定义域的异常,比如尝试对负数求平方根。
  • invalid_argument :表示无效参数引发的异常,比如传递非法格式的字符串。
  • length_error :表示试图创建超出容器最大长度的对象,比如向 vector 添加过多元素。
  • out_of_range :表示访问容器中不存在的元素时抛出的异常,比如使用越界的索引访问 vector

运行时错误异常
runtime_error 是运行时错误的基类,表示在程序运行过程中发生的错误。

  • range_error :表示计算结果超出表示范围的异常。
  • overflow_error :表示算术运算发生上溢的异常。
  • underflow_error :表示算术运算发生下溢的异常。

其他异常

  • ios_base::failure :表示与输入/输出流相关的错误,比如文件读取失败。

拜拜,下期再见😏

摸鱼ing😴✨🎞
请添加图片描述

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

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

相关文章

[SWPUCTF 2022 新生赛]Ez_upload 详细题解

知识点: 文件上传 MIME绕过 script版本一句话木马 .htaccess配置文件 蚁剑虚拟终端的使用 打开题目可以文件上传 传入php文件, 提示 后缀不能是ph 所以也不能输入 phtml php3 pht等可以解析为php文件的后缀 bp抓包把传入的php木马文件后缀手动修改为jpg图片格式 提示 你上…

V20变频器设置电机电流超过设定的值,变频器报警停止运转

之前使用的台达变频器是有相关参数的设置的&#xff0c;比如设置额定电流的限制比例未1.5A时&#xff0c;超过1.5A时&#xff0c;变频器会输出报警信号&#xff0c;并停机报警。换到V20变频时&#xff0c;翻遍了说明书&#xff0c;并各种参数测试组合&#xff0c;未找到明确的相…

网络安全-态势感知

0x00 定义&#xff1a; 态势感知&#xff08;Situation Awareness&#xff0c;SA&#xff09;能够检测出超过20大类的云上安全风险&#xff0c;包括DDoS攻击、暴力破解、Web攻击、后门木马、僵尸主机、异常行为、漏洞攻击、命令与控制等。利用大数据分析技术&#xff0c;态势感…

未完成_RFdiffusion应用案例_从头设计pMHC的结合剂

目录 1. 论文导读1&#xff09;摘要2&#xff09;设计流程3&#xff09;设计流程的验证 2. 实战 1. 论文导读 Liu, Bingxu, et al. “Design of high specificity binders for peptide-MHC-I complexes.” bioRxiv (2024): 2024-11. 1&#xff09;摘要 MHC-I 将胞内抗原肽递呈…

Vant UI Axure移动端元件库:提升移动端原型设计效率

UI框架的选择对于提升开发效率和用户体验至关重要。Vant UI&#xff0c;作为一款基于Vue.js的轻量、可靠的移动端组件库&#xff0c;自2017年开源以来&#xff0c;凭借其丰富的组件库、良好的性能以及广泛的兼容性&#xff0c;在移动端开发领域崭露头角&#xff0c;赢得了众多开…

stm32中的常用函数

目录 一、定义声明类 1.1 预定义 1.2 条件编译 1.3 extern 声明 1.3 typedef 类型别名 1.4 结构体 二、基础函数 2.1 delay类函数 2.2 printf函数 三、GPIO 3.1 硬件 3.2 通用外设驱动模型 3.3 例程 四、中断 4.1. 什么是中断 4.2. NVIC 4.3. EXTI 4.4. EXTI和…

静态路由与交换机配置实验

1.建立网络拓扑 添加2台计算机&#xff0c;标签名为PC0、PC1&#xff1b;添加2台二层交换机2960&#xff0c;标签名为S0、S1&#xff1b;添加2台路由器2811&#xff0c;标签名为R0、R1&#xff1b;交换机划分的VLAN及端口根据如下拓扑图&#xff0c;使用直通线、DCE串口线连接…

【Appium】AttributeError: ‘NoneType‘ object has no attribute ‘to_capabilities‘

目录 1、报错内容 2、解决方案 &#xff08;1&#xff09;检查 &#xff08;2&#xff09;报错原因 &#xff08;3&#xff09;解决步骤 3、解决结果 1、报错内容 在PyCharm编写好脚本后&#xff0c;模拟器和appium也是连接成功的&#xff0c;但是运行脚本时报错&…

【汇编语言】标志寄存器(三) —— 条件跳转,精准决策:汇编语言的比较与转移

文章目录 前言1. 检测比较结果的条件转移指令1.1 什么是条件转移指令&#xff1f;1.2 两类条件转移指令 2. 根据无符号比较的条件转移指令2.1 如何记忆&#xff1f;2.2 如何实现比较转移的功能&#xff1f;2.3 举例说明2.3.1 例12.3.2 例2 3. 总结4. 例题巩固4.1 问题一4.1.1 问…

【Unity高级】如何获取着色器(Shader)的关键词

在动态设置Shader时&#xff0c;会需要通过EnableKeyword, DisableKeyword来完成。但一个Shader有哪些关键词呢&#xff1f;Unity的文档中并没有列出来&#xff0c;但我们可以通过遍历Shader的KeywordSpace来查看。 1. 代码如下 using UnityEngine;public class KeywordExamp…

针对边缘计算优化LoRa的TinyML信道跳变管道

论文标题&#xff1a;Optimizing LoRa for Edge Computing with TinyML Pipeline for Channel Hopping&#xff08;针对边缘计算优化LoRa的TinyML信道跳变管道&#xff09; 作者信息&#xff1a;Marla Grunewald, Mounir Bensalem 和 Admela Jukan&#xff0c;来自德国布伦瑞克…

使用AI工具Screenshot to Code将UI设计图翻译成代码

一、获取openAI apikey。 一般有两种方式&#xff0c;一种是到openAI官网注册账号&#xff0c;付费申请GPT4的apikey。另一种是某宝买代理。我这里采用第二种。 二、安装Screenshot to Code 1.到github下载源码。 2.启动&#xff0c;两种方式&#xff1a;源码启动和docker启动…

python学opencv|读取图像(三)放大和缩小图像

【1】引言 前序已经学习了常规的图像读取操作和图像保存技巧&#xff0c;相关文章链接为&#xff1a; python学opencv|读取图像-CSDN博客 python学opencv|读取图像&#xff08;二&#xff09;保存彩色图像-CSDN博客 今天我们更近一步&#xff0c;学习放大和缩小图像的技巧&…

【vue2自定义指令】v-disabled 实现el-switch,el-button等elementUI禁用(disabled)效果

如果你搜过类似的功能&#xff0c;肯定看到过千篇一律的 // 实现按钮禁用el.disabled true// 增加 elementUI 的禁用样式类el.classList.add(is-disabled)但是这个方案明显对el-switch&#xff0c;不起作用&#xff0c;所以我这边直接把方案贴出来&#xff0c;不想了解具体原理…

烟草行业通过Profinet转EthernetIP网关打通数据壁垒

在工业自动化领域&#xff0c;Profinet转Ethernet/IP是两种广泛应用的工业以太网协议。它们各自具有独特的特点和优势&#xff0c;而在实际应用中&#xff0c;经常需要实现这两种协议之间的互通&#xff0c;这时就需要使用到开疆智能Profinet转Ethernet/IP网关KJ-EIP-108。同时…

C++内存布局以及常用关键字

C内存布局以及常用关键字 C的内存空间 代码存储区域&#xff1a;常量区、代码区、静态区&#xff08;全局区&#xff09;、堆区、栈区 栈区向下增长&#xff0c;堆区向上增长。栈由系统管理&#xff0c;没有内存碎片&#xff0c;每个元素之间都是连续的&#xff0c;大小比较…

插入排序⁻⁻⁻⁻直接插入排序希尔排序

引言 所谓的排序&#xff0c;就是使一串记录按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 常见的排序算法有&#xff1a; 今天我们主要学习插入排序的直接插入排序和希尔排序。 直接插入排序 什么是直接插入排序&#xff1f; 直接插入排序其…

Python实现中国象棋

探索中国象棋 Python 代码实现&#xff1a;从规则逻辑到游戏呈现 中国象棋&#xff0c;这款源远流长的棋类游戏&#xff0c;承载着深厚的文化底蕴与策略智慧。如今&#xff0c;借助 Python 与 Pygame 库&#xff0c;我们能够在数字世界中复刻其魅力&#xff0c;深入探究代码背后…

2024年12月6日Github流行趋势

项目名称&#xff1a;lobe-chat 项目维护者&#xff1a;arvinxx, semantic-release-bot, canisminor1990, lobehubbot, renovate项目介绍&#xff1a;一个开源的现代化设计的人工智能聊天框架。支持多AI供应商&#xff08;OpenAI / Claude 3 / Gemini / Ollama / Qwen / DeepSe…