C++笔记---异常

news2024/12/25 0:22:05

1. 异常的概念

1.1 异常和错误

异常通常是指在程序运行中动态出现的非正常情况,这些情况往往是可以预见并可以在不停止程序的情况下动态地进行处理的。

错误通常是指那些会导致程序终止的,无法动态处理的非正常情况。例如,越界访问、栈溢出、语法错误等等。错误往往无法预见,需要程序员进行调试来发现出错的原因。

1.2 异常处理机制

C++提供了一套异常处理机制,用于管理和控制程序中可能出现的异常。这个机制基于三个关键的关键字:throw、try和catch。

  • throw关键字用于抛出一个异常。异常可以是任何类型的对象,但通常是从std::exception派生的类的实例。
  • try块包围可能会抛出异常的代码。如果在try块中发生异常,程序会立即停止执行当前的函数,并开始在包含try块的函数上下文中搜索匹配的catch块。
  • catch块定义了异常处理代码。每个catch块都有一个异常声明,用于指定它能够捕获的异常类型。当try块中抛出一个异常时,程序会尝试匹配catch块中的异常声明,并执行匹配的catch块中的代码。
try
{
    // 可能抛出异常的代码
    // ...
}
catch(Exception e) 
{
    // 处理异常或显示错误信息的代码
    // ...
}
// 如果需要可继续增加catch块

其中,Exception为可接收异常对象的类型(与异常对象的类型相同,异常对象的父类,异常对象可以发生隐式类型转换的类型)。

与传参的规则相似,能传参给e就能捕获,其中用父类来捕获子类异常在异常继承体系中非常实用,例如:除零异常类继承自算术异常类,那么就可以使用算术异常类来捕获除零异常。 


 2. 异常的抛出与捕获

程序出现问题时,我们通过抛出(throw)一个对象来引发一个异常,该对象的类型以及当前的调用链决定了应该由哪个catch的处理代码来处理该异常。

当异常被抛出后,程序会立即跳转到能捕获该异常的最近的catch块处,也就是说:

(1)当前try块中的代码会立即停止执行,并沿着调用链往回匹配能够捕获该异常的catch块。

(2)在匹配catch块的过程中,当前函数栈帧未能处理掉异常,则函数栈帧会被立即销毁(该函数栈帧中已定义的对象全部进入析构流程)并返回上一层函数调用,继续匹配catch块。

若在返回到main函数之后都未能处理掉异常,那么该异常就成为了一个错误,程序会立即终止并报错。

#include<iostream>
#include<string>
using namespace std;

double Divide(int a, int b)
{
	try
	{
		// 当b == 0时抛出异常
		if (b == 0)
		{
			string s("Divide by zero condition!");
			throw s;
		} 
		else
		{
			return ((double)a / (double)b);
		}
	} 
	catch(int errid)
	{
		cout << "Divide:" << errid << endl;
	} 
	return 0;
} 

void Func()
{
	int len, time;
	cin >> len >> time;
	try
	{
		cout << Divide(len, time) << endl;
	} 
	catch(const char* errmsg)
	{
		cout << "Func:" << errmsg << endl;
	}
    // 除数为0时,此行不会被执行
	cout << __FUNCTION__ << ":" << __LINE__ << "行执行" << endl;
} 

int main()
{
	while (1)
	{
		try
		{
			Func();
		} 
		catch(const string& errmsg)
		{
			cout << "main:" << errmsg << endl;
		}
	} 
	return 0;
}

抛出的异常实质上是异常对象的拷贝,因为被抛出的异常对象可能是一个局部对象,函数栈帧被销毁之后该对象也会被销毁(这里的处理类似于函数传值返回)。

但是这里有一个例外,就是在使用左值引用捕获异常时,异常也能被捕获(一般来说异常对象的拷贝应该是右值,无法使用左值引用接收),且此时引用的是原始异常对象,原始异常对象的生命周期也被延长至catch块末尾。

异常对象的拷贝在catch块运行结束之后销毁。


 3. 异常重新抛出

在catch块中再次使用throw语句会将当前catch块捕获到的异常原样抛出

// 下面程序模拟展⽰了聊天时发送消息,发送失败补货异常,但是可能在
// 电梯地下室等场景⼿机信号不好,则需要多次尝试,如果多次尝试都发
// 送不出去,则就需要捕获异常再重新抛出,其次如果不是网络差导致的
// 错误,捕获后也要重新抛出。
void _SeedMsg(const string& s)
{
	if (rand() % 2 == 0)
	{
		throw HttpException("网络不稳定,发送失败", 102, "put");
	} 
	else if (rand() % 7 == 0)
	{
		throw HttpException("你已经不是对象的好友,发送失败", 103, "put");
	} 
	else
	{
		cout << "发送成功" << endl;
	}
}

void SendMsg(const string& s)
{
	// 发送消息失败,则再重试3次
	for (size_t i = 0; i < 4; i++)
	{
		try
		{
			_SeedMsg(s);
			break;
		} 
		catch(const Exception & e)
		{
			// 捕获异常,if中是102号错误,网络不稳定,则重新发送
			// 捕获异常,else中不是102号错误,则将异常重新抛出
			if (e.getid() == 102)
			{
				// 重试三次以后失败了,则说明网络太差了,重新抛出异常
				if (i == 3)
					throw;
				cout << "网络较差,开始第" << i + 1 << "重试" << endl;
			} 
			else
			{
				throw;
			}
		}
	}
} 

int main()
{
	srand(time(0));
	string str;
	while (cin >> str)
	{
		try
		{
			SendMsg(str);
		} 
		catch(const Exception & e)
		{
			cout << e.what() << endl << endl;
		} 
		catch(...)
		{
			cout << "Unkown Exception" << endl;
		}
	}

	return 0;
}

4. 异常安全问题

4.1 捕获意外的异常和未知异常

前面说过,异常如果未被捕获就会成为错误,所以我们在main函数中一般会这样来写以避免异常未被捕获的情况:

int main()
{
	try
	{
		Func();
	} 
	catch(const Exception & e)
	{
		cout << e.what() << endl << endl;
	} 
	catch(...)
	{
		cout << "Unkown Exception" << endl;
	}

	return 0;
}

其中,这里的Exception代表异常继承体系中所有异常的父类(自定义了异常继承体系或使用了标准库中的异常继承体系),也就是说其可以接收继承体系中任意类型的异常,从而保证意外抛出的异常也能被捕获。

"..."代表任意类型的被抛出的异常,假如被该块捕获,说明该异常不在异常继承体系中,是未知的异常。

4.2 异常处理导致的内存泄露

异常抛出后,后面的代码就不再执行,前面申请了资源(内存、锁等),后面进行释放,但是中间可能会抛异常就会导致资源没有释放,这里由于异常就引发了资源泄漏,产生安全性的问题。

我们可以采取先捕获异常并将资源释放之后重新抛出的方式处理这种情况,但这样做代码的可维护性较差,后面智能指针章节讲的RAII方式解决这种问题是更好的。

double Divide(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 << Divide(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;
	} 
	catch(const exception & e)
	{
		cout << e.what() << endl;
	} 
	catch(...)
	{
		cout << "Unkown Exception" << endl;
	} 
	return 0;
}

其次析构函数中,如果抛出异常也要谨慎处理,比如析构函数要释放10个资源,释放到第5个时抛出异常,则也需要捕获处理,否则后面的5个资源就没释放,也资源泄漏了。《Effctive C++》第8个条款也专门讲了这个问题,别让异常逃离析构函数


5. 异常规范

5.1 异常处理的最佳实践

在使用C++异常处理时,应当遵循一些最佳实践,包括:

  • 只在真正无法通过常规错误处理机制恢复的情况下抛出异常。
  • 尽可能地捕获和处理异常,以提供清晰的错误报告和恢复策略。
  • 不要使用裸的throw语句,总是在try块中使用。
  • 使用noexcept关键字来标记那些不应该抛出异常的函数,这有助于编译器优化性能。

5.2 noexcept关键字

对于用户和编译器而言,预先知道某个程序会不会抛出异常大有裨益,知道某个函数是否会抛出异
常有助于简化调用函数的代码。

C++98中函数参数列表的后面接throw(),表示函数不抛异常,函数参数列表的后面接throw(类型1,类型2...)表示可能会抛出多种类型的异常,可能会抛出的类型用逗号分割。

C++98的方式这种方式过于复杂,实践中并不好用,C++11中进行了简化,函数参数列表后面加noexcept表示不会抛出异常,啥都不加表示可能会抛出异常。

编译器并不会在编译时检查noexcept,也就是说如果一个函数用noexcept修饰了,但是同时又包含了throw语句或者调用的函数可能会抛出异常,编译器还是会顺利编译通过的(有些编译器可能会报个警告)。但是一个声明了noexcept的函数抛出了异常,程序会调用 terminate函数 终止程序。

noexcept(expression)还可以作为一个运算符去检测一个表达式是否有可能会抛出异常,可能会则返回false,不会就返回true。 

// C++98
// 这里表示这个函数只会抛出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
size_type size() const noexcept;
iterator begin() noexcept;
const_iterator begin() const noexcept;

double Divide(int a, int b) noexcept
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		// 假如抛出则会报错
		throw "Division by zero condition!";
	} 
	return(double)a / (double)b;
} 

int main()
{
	try
	{
		int len, time;
		cin >> len >> time;
		cout << Divide(len, time) << endl;
	} 
	catch(const char* errmsg)
	{
		cout << errmsg << endl;
	} 
	catch(...)
	{
		cout << "Unkown Exception" << endl;
	} 
	int i = 0;
	cout << noexcept(Divide(1, 2)) << endl;
	cout << noexcept(Divide(1, 0)) << endl;
	cout << noexcept(++i) << endl;
	return 0;
}

6. C++标准库中的异常继承体系

其中std::exception为所有异常类的父类,其包含一个虚函数what,该函数在被调用后返回异常信息。该继承体系中所有的子异常类都重写了该函数,以表示不同的异常信息。

 具体信息参考:exception - C++ Reference

一般公司中都会写一套自己的异常体系,标准库中的异常体系其实用的不多。

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

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

相关文章

python opencv3

三、图像预处理2 1、图像滤波 为图像滤波通过滤波器得到另一个图像。也就是加深图像之间的间隙&#xff0c;增强视觉效果&#xff1b;也可以模糊化间隙&#xff0c;造成图像的噪点被抹平。 2、卷积核 在深度学习中&#xff0c;卷积核越大&#xff0c;看到的信息越多&#xff0…

ENSP作业——小型园区网

题目 根据上图&#xff0c;可得需求为&#xff1a; 1.配置交换机上的VLAN及IP地址。 2.设置SW1为VLAN 2/3的主根桥&#xff0c;设置SW2为VLAN 20/30的主根桥&#xff0c;且两台交换机互为主备。 3.可以使用super vlan。&#xff08;本次实验中未使用&#xff09; 4.上层通过静…

解决 Vue3、Vite 和 TypeScript 开发环境下跨域的问题,实现前后端数据传递

引言 本文介绍如何在开发环境下解决 Vite 前端&#xff08;端口 3000&#xff09;和后端&#xff08;端口 80&#xff09;之间的跨域问题&#xff1a; 在开发环境中&#xff0c;前端使用的 Vite 端口与后端端口不一致&#xff0c;会产生跨域错误提示&#xff1a; Access to X…

Windows系统中Oracle VM VirtualBox的安装

一.背景 公司安排了师带徒&#xff0c;环境搭建问题一直是初级程序员头疼的事情&#xff0c;我记录一下这些基础的内容&#xff0c;方便初学者。大部分开发者的机器还是windows系统&#xff0c;所以写了怎么安装。 二.版本信息及 操作系统&#xff1a;windows11 家庭版…

uniapp 集成 uview

注意&#xff1a;HBuildX新建项目时必须选择vue2版本&#xff0c;vue3会不支持uview 下载安装方式&#xff1a; uview安装网站&#xff1a;uView2.0重磅发布&#xff0c;利剑出鞘&#xff0c;一统江湖 - DCloud 插件市场 配置&#xff1a; 1.安装sass插件 // 安装sass npm i …

24.11.12 JavaScript2

prompt() confirm() 这些函数 会阻止js解析器(js解析器执行引擎 读取运行js) 执行 不要使用 2history对象 历史记录对象 对应浏览器前进后退按钮 history 在历史记录里 back 前进 forward 后退go 0当前文档 负数 后退n个文档 正数 前进n个文档<!…

STM32cubemx+Proteus仿真和keil5联合调试

前面两步 STM32cubemx生成代码 https://blog.csdn.net/weixin_52733843/article/details/143637304 Proteus新建工程 https://blog.csdn.net/weixin_52733843/article/details/143578853 1 *Proteus仿真联合调试* 在Proteus中&#xff0c;双击STM32F103C6芯片&#xff0c…

信号的解析

信号 1.概念2.接口3.信号产生的过程1.信号的产生1.1信号的产生方式 2.信号的处理3.信号的保存阻塞信号 4.信号集操作函数 1.概念 信号量&#xff08;Semaphore&#xff09;是一个用于多线程或多进程同步的变量。它是操作系统提供的一种同步机制&#xff0c;用于控制多个线程或…

linux-c 使用c语言操作sqlite3数据库-1

一、练习目标 1、目标 1、使用sqlite3_exec执行查询语句&#xff0c;并将查询结果insert到链表中&#xff0c;最后打印链表的内容&#xff1b; 2、使用sqlite3_get_table执行查询语句&#xff0c;并以key&#xff1a;value的方式&#xff0c;打印查询结果。 2、环境准备 2.1、…

软件需求规格书评审报告,系统需求设计申评审,代码和测试过程评审报告,软件各类资质评审资料(word原件)

1.需求规格说明评审报告 2.系统设计评审报告 3.编码与测试评审报告 软件全套资料部分文档清单&#xff1a; 工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#xff0c;产品需求规格说明书&#xff0c;需求调研计划&#xff0c;用户需求调查单&#xff0…

flink sql同步mysql数据表到mysql

1. 关闭防火墙和selinux systemctl stop firewalld systemctl disable firewalld systemctl status firewalld2.安装java8 yum list java-1.8* yum install java-1.8.0-openjdk* -yjava -version3.下载和部署mysql yum -y install wget wget https://dev.mysql.com/get/Down…

【分布式事务】二、NET8分布式事务实践: DotNetCore.CAP 框架 、 消息队列(RabbitMQ)、 多类型数据库(MySql、MongoDB)

介绍 DotNetCore.CAP简称CAP, [CAP]是一个用来解决微服务或者分布式系统中分布式事务问题的一个开源项目解决方案, 同样可以用来作为 EventBus 使用,CAP 拥有自己的特色,它不要求使用者发送消息或者处理消息的时候实现或者继承任何接口,拥有非常高的灵活性。我们一直坚信…

vue3项目中内嵌vuepress工程两种实现方式

目录 一、示例二、创建vuepress工程三、配置vue项目的打包命令四、 通过iframe嵌套实现过程五、 将vue项目打包&#xff0c;启本地服务运行index.html 一、示例 vue项目&#xff0c;点击用户手册按钮&#xff0c;通过a标签跳转到vuepress框架搭建的页面。点击后者通过路由跳转…

智能座舱多屏项目,中控屏切换语言,后排屏闪黑屏问题

1. 背景 智能座舱多屏项目&#xff0c;中控屏切换语言&#xff0c;后排屏闪黑屏问题 2. 详细分析过程 通过events log查看activity的生命周期&#xff1a;adb shell logcat -b events com.android.rwhvac.view.behind.BehindActivity2 : displayId 2 副屏app com.android.…

多商户中英双语电商系统设计与开发 PHP+mysql

随着全球电商市场的扩展&#xff0c;多商户平台成为了越来越多商家参与全球贸易的重要方式。为了适应不同语言用户的需求&#xff0c;尤其是中英双语用户的需求&#xff0c;设计一个支持中英双语的电商系统显得尤为重要。本文将重点探讨如何设计一个多商户中英双语电商系统&…

Ansys Zemax | 手机镜头设计 - 第 4 部分:用LS-DYNA进行冲击性能分析

该系列文章将讨论智能手机镜头模组设计的挑战&#xff0c;从概念和设计到制造和结构变形分析。本文是四部分系列中的第四部分&#xff0c;它涵盖了相机镜头的显式动态模拟&#xff0c;以及对光学性能的影响。使用Ansys Mechanical和LS-DYNA对相机在地板上的一系列冲击和弹跳过程…

【JavaEE初阶】多线程上部

文章目录 本篇目标&#xff1a;一、认识线程&#xff08;Thread&#xff09;1.概念&#xff1a;2.创建线程 二、Thread 类及常见方法2.1 Thread 的常见构造方法2.2 Thread 的几个常见属性2.3 启动⼀个线程 - start()2.4 中断⼀个线程2.5 等待⼀个线程 - join()2.6 获取当前线程…

丹摩征文活动|智谱AI引领是实现文本可视化 - CogVideoX-2b 部署与使用

文章目录 前言一、DAMODEL平台特性二、创建CPU云实例三、CogVedioX介绍四、DAMODEL一键部署CogVideoX1. 创建丹摩实例(参考上述介绍)2. 配置环境和依赖3. 模拟与配置文件4. 开始运行4.1 调试4.2 webUI4.3 端口映射 前言 DAMODEL&#xff08;丹摩智算&#xff09;是一款专为满足…

MySQL的知识巩固

目录 三大范式 第一范式: 第二范式: 第三范式: 巴斯-科德范式(BCNF): 反范式&#xff1a; MySQL的工作原理 三大范式 第一范式: 一个字段只表明一个事情 优点: 数据一致性&#xff1a; 在1NF中&#xff0c;由于每个属性都是原子的&#xff0c;因此避免了在一个属性中存…

越野车TV 1.0.5337 | TV端越野赛车游戏,解锁无限金币

越野车TV是一款专为电视设计的越野赛车游戏&#xff0c;最初是安卓手机端的游戏&#xff0c;现已移植到TV端并兼容遥控器操作。这款游戏的特点是解锁了VIP&#xff0c;所有资源都可以免费使用。游戏采用3D横版卡通风格&#xff0c;提供真实的越野赛车体验。玩家可以在崎岖的赛道…