C++异常详解

news2024/11/16 15:29:11

文章目录

  • 前言
  • 一、C语言传统的处理错误的方式
  • 二、C++异常概念
  • 三、异常的使用
    • 3.1 异常的抛出和捕获
    • 3.2 异常的重新抛出
    • 3.3 异常安全
    • 3.4 异常规范
  • 四、C++标准库的异常体系
  • 五、自定义异常体系
  • 六、异常的优缺点
    • C++异常的优点
    • C++异常的缺点
  • 总结


前言


正文开始!

一、C语言传统的处理错误的方式

传统的错误处理机制:

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

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

二、C++异常概念

异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数直接或间接的调用者处理这个错误。

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

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

	try
	{
	  // 保护的标识代码
	}catch( ExceptionName e1 )
	{
	  // catch 块
	}catch( ExceptionName e2 )
	{
	  // catch 块
	}catch( ExceptionName eN )
	{
	  // catch 块
	}

三、异常的使用

3.1 异常的抛出和捕获

异常的抛出和匹配原则

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

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

  • 首先检查throw本身是否在try块内部,如果是在查找匹配的catch语句。如果有匹配的,则调到catch的地方进行处理。
  • 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。
  • 如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。所以时间中我们最后都要加一个catch(…)捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止。
  • 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。
    在这里插入图片描述
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()
{
	Func();

	//try 
	//{
	//	Func();
	//}
	//catch (const char* errmsg)
	//{
	//	cout << errmsg << endl;
	//}
	//catch (...) 
	//{
	//	cout << "unkown exception" << endl;
	//}
	return 0;
}

不进行捕获的情况
在这里插入图片描述

进行捕获以后

在这里插入图片描述

3.2 异常的重新抛出

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

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;
}

在这里插入图片描述

发生除零错误后
在这里插入图片描述

3.3 异常安全

  • 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化
  • 析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄露(内存泄漏)
  • C++中异常经常会导致资源泄露的问题,比如在new和delete中抛出了异常,导致内存泄露,在lock和unlock直接抛出了异常导致死锁,C++经常使用RAII来解决如上问题,关于RAII我在智能指针的章节详细给大家讲解。

3.4 异常规范

  1. 异常规格说明的目的是为了让函数使用者直到该函数可能抛出的异常有哪些。可以在函数的后面接throw(类型),列出这个函数可能抛出的所有类型。
  2. 函数的后面接throw(),表示函数不抛异常。
  3. 若无异常接口声明,则此函数可以抛出任何类型的异常。
//这里表示这个函数会抛出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 delete(std::size_t size, void* ptr) throw();

//C++11中新增的noexcept,表示不会抛异常
thread() noexcept;
thread(thread&& x) noexcept;

四、C++标准库的异常体系

C++提供了一系列标准的异常,定义在exception的类中,我们可以在程序中使用这些标准的异常。他们是以父子类层次结构组织起来的,如下图所示:

在这里插入图片描述

下表是对以上层次结构中的每个异常的说明:

在这里插入图片描述

#include <iostream>     // std::cout
#include <new>          // std::bad_alloc

int main() {
    int i = 0;
    try
    {
        while (true)
        {
            int* myarray = new int[10000];
            cout << myarray << "->" << i++ << endl;
        }
    }
    catch (std::bad_alloc& ba)
    {
        std::cerr << "bad_alloc caught: " << ba.what() << '\n';
    }
    return 0;
}

在这里插入图片描述

int main()
{
	try {
		vector<int> v(10, 5);
		// 这里如果系统内存不够也会抛异常
		v.reserve(100);
		// 这里越界会抛异常
		v.at(10) = 100;
	}
	catch (const exception& e) // 这里捕获父类对象就可以
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "Unkown Exception" << endl;
	}
	return 0;
}

在这里插入图片描述

**说明:**实际中我们可以去继承exception类实现自己的异常类。但是实际中很多公司像下面一样定义一套自己的异常继承体系,因为C++标准库设计的不够好用。

五、自定义异常体系

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

namespace hulu
{
	// 服务器开发中通常使用的异常继承体系
	class Exception
	{
	public:
		Exception(const string& errmsg, int id)
			:_errmsg(errmsg)
			, _id(id)
		{}
		virtual string what() const
		{
			return _errmsg;
		}
		int getid()const
		{
			return _id;
		}
	protected:
		string _errmsg;// 描述错误信息
		int _id;// 错误编码
		
		//堆栈信息
	};
	class SqlException : public Exception
	{
	public:
		SqlException(const string& errmsg, int id, const string& sql)
			:Exception(errmsg, id)
			, _sql(sql)
		{}
		virtual string what() const
		{
			string str = "SqlException:";
			str += _errmsg;
			str += "->";
			str += _sql;
			return str;
		}
	private:
		const string _sql;
	};
	class CacheException : public Exception
	{
	public:
		CacheException(const string& errmsg, int id)
			:Exception(errmsg, id)
		{}
		virtual string what() const
		{
			string str = "CacheException:";
			str += _errmsg;
			return str;
		}
	};
	class HttpServerException : public Exception
	{
	public:
		HttpServerException(const string& errmsg, int id, const string& type)
			:Exception(errmsg, id)
			, _type(type)
		{}
		virtual string what() const
		{
			string str = "HttpServerException:";
			str += _type;
			str += ":";
			str += _errmsg;
			return str;
		}
	private:
		const string _type;
	};
	void SQLMgr()
	{
		srand(time(0));
		if (rand()<RAND_MAX/10)
		{
			throw SqlException("权限不足", 100, "select * from name = '张三'");
		}
		else
		{
			cout << "Sql success" << endl;
		}

	}
	void CacheMgr()
	{
		if (rand() < RAND_MAX / 10)
		{
			throw CacheException("权限不足", 100);
		}
		else if (rand() < RAND_MAX / 5)
		{
			throw CacheException("数据不存在", 101);
		}
		else
		{
			cout << "Cache success" << endl;
		}
		SQLMgr();
	}

	void SeedMsg(const string& str)
	{
		if (rand() < RAND_MAX / 10)
		{
			throw HttpServerException("SeedMsg::网络错误", 2, "put");
		}
		else if (rand() < RAND_MAX / 5)
		{
			throw HttpServerException("SeedMsg::你已经不是对方的好友", 1, "post");
		}
		else
		{
			cout <<"消息发送成功 -> " << str << endl;
		}
	}

	void HttpServer()
	{
		if(rand() < RAND_MAX / 10)
		{
			throw HttpServerException("请求资源不存在", 404, "get");
		}
		else if (rand() < RAND_MAX / 5)
		{
			throw HttpServerException("权限不足", 501, "post");
		}
		else
		{
			cout << "Http success" << endl;
		}
		CacheMgr();
	}
}
int main()
{
	srand(time(0));
	while (1)
	{
		Sleep(1000);
		try {
			hulu::HttpServer();
			for (int i = 0; i < 10; i++)
			{
				//权限错误就直接报错
				try
				{
					//发送出现网络错误,要求重试10次。
					hulu::SeedMsg("下午一起去吃饭吗?");
					break;
				}
				catch (const hulu::Exception& e)
				{
					if (e.getid() == 2)//异常编码的价值,针对某个错误进行特殊的处理
					{
						cout << "网络错误,重新发送消息!第" <<i<<"次" << endl;
						continue;
					}
					else
					{
						throw e;//异常的重新抛出
					}
				}
			}
			
		}
		catch (const hulu::Exception& e) // 这里捕获父类对象就可以
		{
			// 多态
			cout << e.what() << endl;
		}
		catch (const std::exception& e) // 这里捕获父类对象就可以
		{
			// 多态
			cout << e.what() << endl;
		}
		catch (...)
		{
			cout << "Unkown Exception" << endl;
		}
	}
	return 0;
}

在这里插入图片描述

六、异常的优缺点

C++异常的优点

  1. 异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的bug。
  2. 返回错误码的传统方式有个很大的问题就是在函数调用链中,深层的函数返回了错误,那么我们得层层返回错误,最外层才能拿到错误。
  3. 很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库,那么我们使用它们也需要使用异常。
  4. 部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如T& operator[](size_t pos)这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。

C++异常的缺点

  1. 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会导致我们跟踪调试时以及分析程序时,比较困难。
  2. 异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计。
  3. **C++没有垃圾回收机制,资源需要自己管理。**有了异常非常容易导致内存泄露、死锁等异常安全问题。这个需要使用RAII来处理资源的管理问题,学习成本比较高。
  4. C++标准库的异常体系定义的不好,还想看堆栈等都不支持,导致大家各自定义自己的异常体系,非常的混乱。
  5. 异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。所以异常规范有两点:一、抛出异常类型都继承自一个基类。二、函数是否抛出异常、抛什么异常,都建议使用func() throw();的方式规范化。

总结

异常总体而言,利大于弊,所以在工程中还是鼓励使用异常的。另外OO的语言基本都是用异常处理错误,这也可以看出是大势所趋。
(本章完!)

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

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

相关文章

大数据:Hive3.x安装部署和配置

文章目录Hive 简介一&#xff0c;Hive 下载和安装1&#xff09;下载hive2&#xff09;安装hive二&#xff0c;Hive 部署1&#xff09;添加hive-site.xml配置2&#xff09;上传mysql jdbc驱动到hive安装包lib下3&#xff09;初始化元数据库4&#xff09;启动hive5&#xff09;be…

数字时代,你还不了解数据仓库吗?

事实上&#xff0c;很多人在看到数据仓库的第一眼&#xff0c;就把数据仓库当成了数据库。当然这也很正常&#xff0c;毕竟从名字来看这两者确实区别不大&#xff0c;真正区分两者的是定位、作用等更深一层的东西。今天我们就来详细了解一下数据仓库&#xff0c;从概念、特点、…

【网络】一文带你了解计算机网络基础

我们在学校中&#xff0c;相信大家都学过计算机四门必修课之一的计算机网络&#xff0c;那么今天简单的总结一下计算机网络的基础和原理&#xff0c;有需要朋友&#xff0c;可以点赞收藏。 一文带你了解计算机网络基础1、 网络 7 层架构2、 TCP/IP原理2.1 网络访问层(Network A…

MongoDB的入门及使用

文章目录1、MongoDB组成2、基本指令3、安装Studio 3T4、操作语句db操作collection操作document操作document 查询5、开发使用1、MongoDB组成 MongoDB中有三个概念需要大家了解&#xff1a;数据库、集合、文档 数据库database&#xff1a; 数据库是一个仓库&#xff0c;在仓库中…

【Docker】安装容器(Tomcat/MySQL/Redis)

文章目录Docker安装镜像容器安装 Tomcat安装 MySQL安装 MySQL实战版安装 RedisDocker安装镜像容器 步骤 搜索镜像 拉取镜像 查看镜像 启动镜像 - 服务端口映射 停止容器 移除容器 安装 Tomcat 1、DockerHub上查找Tomcat镜像 docker search tomcat 2、从DockerHub拉取Tomcat镜…

volatile synchronized Lock

参考博客&#xff1a;https://www.cnblogs.com/cg-ww/p/14540450.html https://zhuanlan.zhihu.com/p/563597528?utm_id0 synchronized修饰变量&#xff0c;https://blog.csdn.net/dlf123321/article/details/53515756 详解synchronized关键字&#xff0c;https://zhuanlan.zh…

一文回顾Aleo到底有多卷

年底的Aleo是区块链行业的一场大事&#xff0c;由于融资超过两亿美元这样的光环围绕&#xff0c;Aleo出道即备受关注&#xff0c;网上不少教程也是满天飞&#xff0c;甚至在激励测试开始之前&#xff0c;就已经非常卷了&#xff0c;而大家都在认为激励测试在两三周之后开始&…

java swing(GUI) mysql实现的酒店管理系统源码文档教程

今天给大家演示一下由我修订开发的一款由Java swing mysql实现的酒店管理系统&#xff0c;以前是sqlserver版本的&#xff0c;我修改了驱动&#xff0c;做了整体升级&#xff0c;改成了mysql版本的&#xff0c;系统功能非常完善&#xff0c;实现了酒店宾馆日常的所有操作功能&a…

28-Vue之ECharts-折线图

ECharts-折线图前言折线图特点折线图实现步骤折线图常见效果标记线条控制填充风格紧挨边缘缩放, 脱离0值比例堆叠图前言 本篇来学习下折线图的实现 折线图特点 折线图更多的使用来呈现数据随时间的变化趋势 折线图实现步骤 ECharts 最基本的代码结构准备x轴的数据准备 y 轴…

高级网络应用复习——三层和生成树实验 加 命令

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.实验 1. 实验要求 2.实验命令 三层配置 和三层交换机连接…

双十二有哪些高性价比的电容笔?十大电容笔知名品牌

任何东西都有它独特的意义、存在和作用。随着ipad的影响力越来越大&#xff0c;ipad的用户数量也越来越多&#xff0c;所以要提高ipad的性能&#xff0c;就需要一款合适的电容笔。那么&#xff0c;电容笔该选择哪个品牌&#xff1f;我将向大家推荐几款性价比高的电容笔&#xf…

PPa-HA/NH2/NHS/MAL焦脱镁叶绿酸-a修饰叶酸/氨基/活性酯/马来酰亚胺的反应

小编下面给大家分享的科研内容是类PPa-HA/NH2/NHS/MAL焦脱镁叶绿酸-a修饰叶酸/氨基/活性酯/马来酰亚胺的反应&#xff0c;和小编一起来看看&#xff01; PPa-HA焦脱镁叶绿酸-a修饰叶酸的反应&#xff1a; 以脱镁叶绿酸-a甲酯为起始原料,在二氯甲烷中于不同温度下与N-溴代丁二酰…

ChatGPT面试阿里P6测试开发岗能过吗?

最近ChatGPT爆火&#xff0c;ChatGPT能干什么呢&#xff1f;想必已经看过很多文章了&#xff0c;例如ChatGPT通过美国高考、ChatGPT开发游戏、调试代码、写文章等等。 哈哈&#xff0c;作为一个软件测试博主&#xff0c;我怎么可能不出来搞点事情呢&#xff1f;突发奇想&#…

SpringBoot集成Swagger3接口文档及添加Authorization授权

swagger可以为前端提供接口文档及接口测试功能&#xff0c;后端集成起来很方便&#xff0c;对代码也没有入侵&#xff0c;全注解完成&#xff0c;非常好用。 一、集成基础功能 第一步、添加依赖 <!-- swagger3 --><dependency><groupId>io.springfox</g…

28845-97-8,Ac-K-DAla-DAla-OH

Ac-Lys-D-Ala-D-Ala was tested as substrate in a substrate specificity study of the Streptomyces albus DD-carboxypeptidase. 以Ac-Lys-D-Ala-D-Ala为底物&#xff0c;对albus链霉菌dd -羧肽酶进行底物特异性研究。 编号: 172467中文名称: 三肽D丙氨酸羧肽酶底物CAS号: 2…

块交织器5×5 verilog设计及仿真实现

设计题目要求: 本设计基于verilog语言实现,在quartus II下仿真实现: 仿真如下: 全部代码如下: library ieee; use ieee.std_logic_arith.all; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; entity decoder is --实体说明 port(datain,clk,clr:IN…

汽车轻量化 | 某SUV车型白车身的轻量化分析案例

作者 | 王山 仿真秀科普作者 导读&#xff1a;轻量化是当今各整车厂在产品开发中无法回避的问题。当考虑到成本与工艺方面时&#xff0c;更是不容易解决的问题。对于高端车型&#xff0c;其较高的设计与生产成本允许其采用先进的轻量化设计与生产工艺&#xff0c;如碳纤维复合…

函数:先序输出叶结点

问题引入 【问题描述】按照先序遍历的顺序输出给定二叉树的叶结点。 【输入形式】以字符形式输入二叉树的结点序列 输入的字符序列为&#xff1a;ABC##DE#G##F### 【输出形式】输出二叉树中叶子结点的先序序列 【样例输入】 ABC##DE#G##F### 【样例输出】 CGF 【输出形式】输出…

分布式操作系统 - 8.分布式容错管理

文章目录1.容错性概述1.1 基本概念1.2 失效&#xff08;失败&#xff09;类型1.3 失效&#xff08;失败&#xff09;模型&#xff08;halting failure&#xff09;1.4 基于冗余的失效屏蔽技术&#xff08;1&#xff09;冗余类型&#xff08;2&#xff09;三模冗余方法&#xff…

java计算机毕业设计ssm影院售票系统6fg71(附源码、数据库)

java计算机毕业设计ssm影院售票系统6fg71&#xff08;附源码、数据库&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。…