【C++11】异常

news2025/1/17 4:12:37

🌈1.C语言传统处理错误的方式

在讲解C++的异常机制之前我们先来复习一下传统的处理错误的方式。

传统的错误处理机制:

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

实际中C语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误。 

🌈2.C++异常概念

异常是一种处理错误得方式,当一个函数发现自己无法处理的错误时就可以抛异常,让函数直接的或者间接的调用者处理这个错误。异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:trycatchthrow:

  • throw: 当问题出现,程序通过throw抛出一个异常;
  • catch: 在你想要处理问题的地方,通过异常处理程序捕获异常;
  • try: try 块中的代码标识将被激活的特定异常。它后面允许跟着一个或多个 catch 块;
    使用 try/catch 语句的语法:

如果有一个块抛出一个异常,捕获异常的方法会使用try和catch关键字。try块中放置可能抛异常的代码,try块中的代码被称为保护代码。使用try/catch语句的语法如下:

try 
{
     // 保护代码
}

catch( ExceptionName e1 ) 
{
     // catch 块
}

catch( ExceptionName e2 )
 {
    // catch 块
}

catch( ExceptionName eN ) 
{
    // catch 块
}

🌈 3.异常的使用

🍄3.1异常的抛出和使用

🌼异常的抛出和匹配原则:

  • 1.异常是通过抛出对象而引发的,该对象的类型决定了应该激活那个catch的处理代码。
  • 2.被选中的处理代码(catch块)是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
  • 3.抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(类似于函数的传值返回)
  • 4.catch(...)可以捕获任意类型的异常,但捕获后无法知道异常错误是什么。
  • 5.实际异常的抛出和捕获的匹配原则有个例外,捕获和抛出的异常类型并不一定要完全匹配,可以抛出派生类对象,使用基类捕获。

异常必须捕获,不捕获就会报错。


🌼在函数调用链中异常展开栈展开匹配原则

  1. 当异常被抛出后,首先检查throw本身是否在try块内部如果在则查找匹配的catch语句,如果有匹配的,则跳到catch的地方进行处理。
  2. 如果当前函数栈没有匹配的catch则退出当前函数栈,继续在上一个调用函数栈中进行查找匹配的catch。找到匹配的catch子句并处理以后,会沿着catch子句后面继续执行,而不会跳回到原来抛异常的地方。
  3. 如果到达main函数的栈,依旧没有找到匹配的catch,则终止程序
  4. 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。

 例如以下代码:

#include<iostream>
using namespace std;

double Division(int a, int b)
{
	//当b==0时抛异常
	if (b == 0)
		throw "Division by zero condition!";
	else
		return ((double)a / (double)b);
}

void Func()
{
	int len, time;
	cin >> len >> time;
	cout << Division(len, time) << endl;
}

int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	return 0;
}

当Division中的异常被抛出后:

  • 首先会检查throw本身是否在try块内部,这里由于throw不在try块内部,因此会退出Division所在的函数栈,继续在上一个调用函数栈中进行查找,即Func所在的函数栈。
  • Func中也没有匹配的catch,于是就会在main所在的函数栈中进行查找,最终在main函数栈中找到了匹配的catch。
  • 这时就会跳到main函数中对应的catch块中执行对应的代码块,执行完后继续执行该代码块后续的代码.

此代码抛出了一个类型为 const char* 的异常,因此捕获异常时,须在 catch 块中使用 const char*。

  • 如果您想让 catch 块能够处理 try 块抛出的任何类型的异常,则必须在异常声明的括号内使用省略号 ...,如下所示:
try {
 // 保护代码
 }
catch(...) {
 // 能处理任何异常的代码
}

这个可以防止出现未捕获的异常时程序崩溃。

🍄3.2异常的重新抛出 

有时候单个的catch可能不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可通过重新抛出将异常传递给更上层的函数进行处理。

但如果直接让最外层捕获异常进行处理可能会引发一些问题。

double Division(int a, int b)
{
	if (b == 0)  //b==0时会抛出异常
	{
		throw" Division by zero condition!";
	}
	return (double)a / (double)b;
}
void Func()
{
	int* array = new int[10];
	int len, time;
	cin >> len >> time;
	cout << Division(len, time) << endl;

	cout << "delete []" << array << endl;
	delete[] array;

}

int main()
{
	try 
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	return 0;
}

  •  我们就先看Func功能函数那一块,先new一块空间,然后就可以通过delete释放了,这种情况是未抛异常是的运行,但是肯定会有点不正常的情况--------->>>>>>>
  • 如果说Division函数抛出一个异常,这时会直接跳转到main函数中的catch块执行对应的异常处理程序,并且在处理完后继续沿着catch块往后执行。
  • 这时就导致Func中申请的内存块没有得到释放,造成了内存泄露。

  • 这时可以在Func中先对Func抛出的异常进行捕获,捕获后先将申请到的内存释放再将异常重新抛出,这时就避免了内存泄露。
void Func()
{
	int* array = new int[10];
	int len, time;
	cin >> len >> time;
    try
    {
    	cout << Division(len, time) << endl;
    }

    catch(...)
    {
       cout << "delete []" << array << endl;
	   delete[] array;
 
	   throw;  //捕获什么抛出什么
    }
    cout << "delete []" << array << endl;
	delete[] array;

}

🍄3.3异常安全问题

将抛异常导致的安全问题叫做异常安全问题,对于异常安全问题下面给出几点建议:

  1. 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化。
  2. 析构函数主要完成对象资源的清理,最好不要在析构函数中抛出异常,否则可能导致资源泄露(内存泄露、句柄未关闭等)。
  3. C++中异常经常会导致资源泄露的问题,比如在new和delete中抛出异常,导致内存泄露,在lock和unlock之间抛出异常导致死锁,C++经常使用RAII的方式来解决以上问题。

🍄3.5异常规范 

为了让函数使用者知道某个函数可能抛出哪些类型的异常,C++标准规定:

  1. 在函数的后面接throw(type1, type2, ...),列出这个函数可能抛掷的所有异常类型。
  2. 在函数的后面接throw()或noexcept(C++11),表示该函数不抛异常。
  3. 若无异常接口声明,则此函数可以抛掷任何类型的异常。(异常接口声明不是强制的)。

例如以下代码:

//表示func函数可能会抛出A/B/C/D类型的异常
void func() throw(A, B, C, D);
//表示这个函数只会抛出bad_alloc的异常
void* operator new(std::size_t size) throw(std::bad_alloc);
//表示这个函数不会抛出异常
void* operator new(std::size_t size, void* ptr) throw();

🌈4.自定义异常体系 

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

查看源图像

 例如以下代码:

//父类接口
class Exception
{
public:
	Exception(int errid = 0, const string& errmsg = "")
		:ErrId(errid)
		, ErrMsg(errmsg)
	{}


	virtual string what() const
	{
		return ErrMsg;
	}
protected:
	int ErrId;
	string ErrMsg;//异常消息
};

//子类进行继承
//定义一个内存错误
class MemException :public Exception
{
public:
	MemException(int errid = 0, const string& errmsg = "", const string& memstring = "")
		:Exception(errid, errmsg)
		, MemString(memstring)
	{}

	virtual string what() const
	{
		string str = "MemError: ";
		str += ErrMsg;
		str += "[Mem:";
		str += MemString;
		str += "]";

		return str;
	}

private:
	string MemString;
};

//定义一个链接错误
class LinkException :public Exception
{
public:
	LinkException(int errid = 0, const string& errmsg = "", const string& linkstring = "")
		:Exception(errid, errmsg)
		, LinkString(linkstring)
	{}

	virtual string what() const
	{
		string str = "LinkError:";
		str += ErrMsg;

		str += "[Link:";
		str += LinkString;
		str += "]";

		return str;
	}

private:
	string LinkString;
};

void func()
{
	int val = rand() % 10;

	if (val == 2)//假设为内存错误
	{
		MemException e(1, "内存错误", "申请内存过大");//构建一个异常对象
		throw e;
	}
	else if (val == 3)//假设为链接错误
	{
		LinkException e(2, "链接错误", "链接对象过多");//构建一个链接异常对象
		throw e;
	}
	else if (val == 4)
	{
		vector<int>(1000000000, 110000000);//使用系统异常
	}

}
int main()
{

	srand((unsigned)time(NULL));
	for(int i=0;i<100;i++)
	{
		try
		{
			func();
		}
		catch (const Exception& e)
		{
			cout << "自定义异常体系:" << e.what() << endl;
		}
		catch (const exception& e)
		{
			cout << "C++自带异常体系" << e.what() << endl;
		}
		catch (...)
		{
			cout << "未知异常" << endl;
		}
	}

	return 0;
}

(这个代码是@今天我要写bug大佬写的,我借用以下)


 🌈5.C++标准库的异常体系

 C++标准库当中的异常也是一个基础体系,其中exception就是各个异常类的基类,我们可以在程序中使用这些标准的异常,它们之间的继承关系如下:

71. 异常

 71. 异常

 

🌈6.异常的优缺点 

  • 异常的优点:
  1. 异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用等信息,这样可以帮助更好的定位程序的bug
  2. 返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那么我们得层层返回错误码,最终最外层才能拿到错误。
  3. 很多的第三方库都会使用异常,比如boost、gtest、gmock等等常用的库,如果我们不用异常就不能很好的发挥这些库的作用。
  4. 很多测试框架也都使用异常,因此使用异常能更好的使用单元测试等进行白盒的测试。
  5. 部分函数使用异常更好处理,比如T& operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。
  • 异常的缺点:
  1. 异常会导致程序的执行流乱跳,并且非常的混乱,这会导致我们跟踪调试以及分析程序时比较困难。
  2. 异常会有一些性能的开销,当然在现代硬件速度很快的情况下,这个影响基本忽略不计。
  3. C++没有垃圾回收机制,资源需要自己管理有了异常非常容易导致内存泄露、死锁等异常安全问题。这个需要使用RAII来处理资源的管理问题,学习成本比较高。
  4. C++标准库的异常体系定义得不够好,导致大家各自定义自己的异常体系,非常的混乱。
  5. 异常尽量规范使用,否则后果不堪设想,随意抛异常,也会让外层捕获的用户苦不堪言。
  6. 异常接口声明不是强制的,对于没有声明异常类型的函数,无法预知该函数是否会抛出异常。

既然C++11提出了异常,那它就是利大于弊的,但是各位小伙伴在使用时一定要注意操作规范!

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

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

相关文章

Apache POI导入导出excel文件实战

文章目录前言技术栈1、引入依赖2、导入代码实现3、导出代码实现3.1、准备导出文件模板3.2、导出代码实现4、代码实现解释5、常见问题前言 这两天公司项目业务提出需求&#xff0c;要求在前端上传excel文件然后解析展示&#xff0c;因此写篇文章记录一下实现。 技术栈 spring…

抖音小程序实践三:接口开发指南

通过官方文档可以更系统的学习到所有的接口&#xff0c;我这边罗列一下我自己用到测试过的接口供大家参考。 前端-小程序对接官方文档&#xff1a;https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/api/open-interface/user-information/tt-get-user-info服务端-小…

个人黄金准备与须知

投资黄金可以为自己的资产保值&#xff0c;也可以福泽后人&#xff0c;因此十分符合国人传统文化特点和理财智慧。可是现在市场中可以投资黄金的平台那么多&#xff0c;投资者必须先认真“调查研究”&#xff0c;才能“去芜存菁”&#xff0c;选到值得托付的好平台。 其实投资者…

为笔记本电脑绑定公网IP随时随地BT做种完整方案(frp加v2ray配合比特彗星点亮绿灯)

BT做种需要拥有固定的IP(IPV4)和端口&#xff0c;如果想在拥有固定IP的服务端做种&#xff0c;可以直接参考前一期https://www.v2fy.com/p/2022-12-25-bt-1671963832000/ &#xff0c;如果想使用笔记本随时随地做种&#xff0c;那就需要拥有固定IP的服务器进行流量转发。 本篇…

富特科技在创业板IPO过会:计划募资约9亿元,股东包括小米等

近日&#xff0c;深圳证券交易所披露的信息显示&#xff0c;浙江富特科技股份有限公司&#xff08;下称“富特科技”&#xff09;获得创业板上市委会议审核通过。据贝多财经了解&#xff0c;富特科技于2022年6月16日在创业板递交招股书。 本次冲刺创业板上市&#xff0c;富特科…

【聆思CSK6 视觉AI开发套件试用】头肩、手势识别体验与PWM舵机控制

本篇文章来自极术社区与聆思科技组织的CSK6 视觉AI开发套件活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;酷电玩家 环境搭建 官方文档详细环境搭建教程&#xff1a;环境搭建 1、下载Git进行安装。 2、安装lisa zep工具&#xff0c;并初始化 CSK6…

洛谷千题详解 | P1026 [NOIP2001 提高组] 统计单词个数【C++、Java语言】

博主主页&#xff1a;Yu仙笙 专栏地址&#xff1a;洛谷千题详解 目录 题目描述 输入格式 输出格式 输入输出样例 解析&#xff1a; C源码&#xff1a; Java源码&#xff1a; C源码2&#xff1a; ----------------------------------------------------------------------------…

vector模板的简易实现

这篇文章&#xff0c;我们模拟一下STL里面的vector的实现。但是会简化一些内容&#xff0c;让大家能够更好的理解。模拟实现的目的不是为了更好的造轮子&#xff0c;而是为了更好的理解这些容器。 文章目录1. 成员变量2. push_back函数3. reserve函数4. pop_back函数和下标运算…

QT 多线程中使用QCanBusDevice进行PCAN通讯时,无法正常发出数据

QT 多线程中使用QCanBusDevice进行PCAN通讯时&#xff0c;无法正常发出数据 前言 我一开始的代码逻辑是&#xff0c;PCAN开启、关闭、发送、接收这些功能整合在一个工具类中&#xff0c;这个工具类的对象是在主线程创建的&#xff0c;然后我有一个要循环定时发送的功能是独立…

与企企通强强联手!哈尔斯二期数字化采购项目正式启动

近日&#xff0c;浙江哈尔斯真空器皿股份有限公司&#xff08;以下简称“哈尔斯”&#xff09;联合企企通举办二期数字化采购项目启动会&#xff0c;旨在助力哈尔斯实现采购数字化全面升级&#xff0c;提升自主品牌竞争力。会上&#xff0c;双方就该项目的建设方案、项目资源、…

铝合金表面处理废水除铝工艺

铝型材表面处理用水量大&#xff0c;产生废水多&#xff0c;废水中有害物质持续排放。如不加以处理必将污染环境。同时伴随着我国对排污量的征税&#xff0c;也会增加企业的成本和负担。因此&#xff0c;从企业的社会责任和效益两方面考虑&#xff0c;进行废水处理是必须和必要…

解决VsCode启动Vue项目报错:‘vue-cli-service‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。

问题描述 最近居家办公&#xff0c;网速不太稳定&#xff0c;开会的时候网络也是断断续续的&#xff0c;今天需要拉下前端项目运行起来 在我执行npm i下载包的时候&#xff0c;我看到网络超时的错误警告就感觉不太秒。知道大概率要启动失败了 果不其然执行npm run serve的时…

窃取信息的新恶意软件通过假冒的破解网站感染使用者

©网络研究院 一种名为“RisePro”的新型信息窃取恶意软件正在通过由 PrivateLoader 按安装付费 (PPI) 恶意软件分发服务运营的虚假破解站点进行分发。 RisePro 旨在帮助攻击者从受感染的设备中窃取受害者的信用卡、密码和加密钱包。 本周Flashpoint 和 Sekoia的分析师发…

前端框架 Nuxt3 集成 Pinia

目录 一、Nuxt3集成Pinia 二、Pinia的使用 state的使用 1、基本使用及动态渲染 2、state的重置 3、批量更改state数据 getters的使用 1、getters的基本使用 2、getters传参 actions的使用 1、actions的基本使用 一、Nuxt3集成Pinia 参考官方文档&#xff1a;简介 |…

【JavaSE】常用类(447~515)

String 447.常用类-每天一考 1.画图说明线程的生命周期&#xff0c;以及各状态切换使用到的方法等 状态&#xff0c;方法 2.同步代码块中涉及到同步监视器和共享数据&#xff0c;谈谈你对同步监视器和共享数据的理解&#xff0c;以及注意点。 synchronized(同步监视器){//操…

消息队列RabbitMQ学习笔记(五)高级特性

1. 发布确认高级 在生产环境中由于一些不明原因&#xff0c;导致 RabbitMQ 重启&#xff0c;在 RabbitMQ 重启期间生产者消息投递失败&#xff0c; 导致消息丢失&#xff0c;需要手动处理和恢复。于是&#xff0c;我们开始思考&#xff0c;如何才能进行 RabbitMQ 的消息可靠投…

ccc-sklearn-11-线性回归(1)

1.线性回归概述 回归需求在现实中非常多&#xff0c;自然也有了各种回归算法。最著名的就是线性回归和逻辑回归&#xff0c;衍生出了岭回归、Lasso、弹性网&#xff0c;以及分类算法改进后的回归&#xff0c;如回归树、随机森林回归、支持向量回归等&#xff0c;一切基于特征预…

自定义卷积实现卷积的重参数【手撕代码】

在我的上篇文章中主要对RepVGG进行了解析【RepVGG网络中重参化网络结构解读】&#xff0c;里面详细的对论文中的代码进行了解析&#xff0c;展示了RepVGG在重参数时是如何将训练分支进行合并的&#xff0c;总的一句话就是在推理阶段&#xff0c;会将1x1以及identity分支以paddi…

vivo 游戏中心低代码平台的提效秘诀

作者&#xff1a;vivo 互联网服务器团队- Chen Wenyang 本文根据陈文洋老师在“2022 vivo开发者大会"现场演讲内容整理而成。公众号回复【2022 VDC】获取互联网技术分会场议题相关资料。 在互联网流量见顶和用户需求分层的背景下&#xff0c;如何快速迭代产品功能&#xf…

函数模板-C11/17/14

函数模板 文章目录函数模板定义函数模板使用函数模板样例两阶段翻译 Two-Phase Translation模板的编译和链接问题多模板参数引入额外模板参数作为返回值类型让编译器自己找出返回值类型将返回值声明为两个模板参数的公共类型样例默认模板参数样例重载函数模板模板函数特化非类型…