【C++进阶9】异常

news2024/11/26 12:33:26

在这里插入图片描述

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

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

二、C++异常概念

异常:函数无法处理的错误就可以抛出异常
让函数的直接或间接的调用者处理这个错误

  • throw: 出现问题时,程序抛出一个异常
    通过使用 throw 关键字来完成
  • catch: 在您想要处理问题的地方
    通过异常处理程序捕获异常 .catch 关键字用于捕获异常
    可以有多个catch进行捕获
  • try: try 块中的代码标识将被激活的特定异常
    它后面通常跟着一个或多个 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()
{
	try
	{
		Func();
	}
	catch (const char* str) // catch 得跟"Division by zero condition!"类型匹配
	{
		cout << str << endl; // Division by zero condition!
	}

	return 0;
}

2.1 异常的抛出和匹配原则

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

正常的异常抛出包含
错误码和错误描述

class Exception
{
public:
	Exception(int errid, const string& msg)
		: _errid(errid)
		, _errmsg(msg)
	{}

	const string& GetMsg() const // 引用返回,成员变量出了作用域还在
	{
		return _errmsg;
	}

	int GetErrid() const
	{
		return _errid;
	}

private:
	int _errid; // 错误码
	string _errmsg; // 错误描述
};

double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		// Exception err(1, "除0错误");
		// throw err;
		throw Exception(1, "除0错误"); // 用匿名对象,连续的构造+拷贝编译器会优化
	}
	else
	{
		return ((double)a / (double)b);
	}
}

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

	cout << "void Func()" << endl;
}

int main()
{
	try
	{
		Func();
	}
	catch (const Exception& e) 
	{
		cout << e.GetMsg() << endl;
	}
	catch (...) // 捕获任意类型的异常,一般放到最后,防止有些异常没捕获到,导致程序终止
	{
		cout << "未知异常" << endl; 
	}

	return 0;
}

一个公司里面每个人对抛异常的需求不一样
有些人可能只需要一个错误码
有些人则需要更详细的信息

解决方法: 可以搞一个继承体系
比如你是缓存部分我是网络部分
可以写一个子类继承父类Exception
捕获的时候捕两个部分
一个Exception
一个是未知异常
C++很灵活的地方抛子类捕父类

三、自定义异常体系

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

class Exception
{
public:
   Exception(int errid, const string& msg)
   	: _errid(errid)
   	, _errmsg(msg)
   {}

   virtual string what() const // 引用返回,成员变量出了作用域还在
   {
   	return _errmsg;
   }

   int GetErrid() const
   {
   	return _errid;
   }

protected: // 子类可见用保护
   int _errid; // 错误码
   string _errmsg; // 错误描述
};

class SqlException : Exception // sql专属继承
{
public:
   SqlException(int errid, const string& msg, const string& sql) // 派生类必须调用父类的构造函数,派生类的特点必须得去复用父类
   	: Exception(errid, msg)
   	, _sql(sql)
   {}

   virtual string what() const // exception写成虚函数进行重写,构造需要的错误信息进行返回
   {
   	string msg = "SqlException:";
   	msg += _errmsg;
   	msg += "->";
   	msg += _sql;

   	return msg;
   }

protected:
   string _sql;
};

// 出现数据库错误或网络错误
class CacheException : public Exception
{
public:
   CacheException(const string& errmsg, int id)
   	:Exception(id, errmsg)
   {}
   
   virtual string what() const
   {
   	string msg = "CacheException:";
   	msg += _errmsg;

   	return msg;
   }
};

class HttpServerException : public Exception
{
public:
   HttpServerException(const string& errmsg, int id, const string& type) // 外加一个类型错误
   	:Exception(id, errmsg)
   	, _type(type)
   {}
   
   virtual string what() const
   {
   	string msg = "HttpServerException:";
   	msg += _errmsg;
   	msg += "->";
   	msg += _type; 

   	return msg;
   }

private:
   const string _type;
};

void SQLMgr() // 数据库
{
   srand(time(0));
   if (rand() % 7 == 0)
   {
   	throw SqlException(100, "权限不足", "select * from name = '张三'");
   }
   cout << "调用成功" << endl; // 3.数据库出错抛异常,数据库没出错,调用成功.数据库取到数据进行返回 
}

void CacheMgr() // 缓存
{
   srand(time(0));
   if (rand() % 5 == 0)
   {
   	throw CacheException("权限不足", 100);
   }
   else if (rand() % 6 == 0)
   {
   	throw CacheException("数据不存在", 101);
   }
   SQLMgr(); // 2.缓存出错抛异常,没出错调用数据库
}

void HttpServer() // 网络
{
   // 模拟服务出错
   srand(time(0));
   if (rand() % 3 == 0)
   {
   	throw HttpServerException("请求资源不存在", 100, "get");
   }
   else if (rand() % 4 == 0)
   {
   	throw HttpServerException("权限不足", 101, "post");
   }
   CacheMgr(); // 1.网络出错抛异常,没出错调用缓存
}

int main()
{
   while (1)
   {
   	this_thread::sleep_for(chrono::seconds(1));
   	try {
   		HttpServer();
   	}

   	 //catch (const HttpServerException& e) // 当子类和父类同时出现,按顺序走,优先匹配位置近的
   	 //{
     //	cout << "子类";
     //	cout << e.what() << endl;
     //}

   	catch (const Exception& e) // 这里捕获父类对象就可以
   	{
   		// 通过多态拿错误信息.多态指向子类,通过调用子类拿到错误信息 
   		cout << e.what() << endl; 
   		// e是父类对象,抛的可能是父类,调的就是父类的what.抛的如果是sql,指向的是子类,调what调用的则是子类的what
   	}
   	
   	catch (...)
   	{
   		cout << "Unkown Exception" << endl; // 捕捉未知异常
   	}
   }

   return 0;
}

3.1 异常的重新抛出

有可能单个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]; // 如果抛异常会发生内存泄漏
		
	int len, time;
	cin >> len >> time;

	try // 所以捕获异常
	{
		cout << Division(len, time) << endl;
	}
	/*catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}*/

	//catch (const char* errmsg)
	//{
	//	cout << "delete []" << array << endl; 
	//	delete[] array;

	//	throw errmsg; // 重新抛出异常,抛之前释放一下
	//}

	catch (...) // 各种抛错异常,需要不同的捕获方法,用...便可一劳永逸
	{
		cout << "delete []" << array << endl; 
		delete[] array;

		throw; // 异常的重新抛出,捕到什么抛什么

	}

	cout << "delete []" << array << endl; // 如果期望在main函数执行异常,且能够继续完成释放,这是就可以用异常的重新抛出
	delete[] array;
}

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

	return 0;
}

需要注意的是:
构造函数完成对象的构造和初始化
不要在构造函数中抛出异常
否则可能导致对象不完整
或没有完全初始化

析构函数主要完成资源的清理
不要在析构函数内抛出异常
否则可能导致资源泄漏

C++中异常经常导致资源泄漏的问题
比如:
在new和delete中抛出异常导致内存泄漏
在lock和unlock之间抛出异常导致死锁
C++经常使用RAII来解决以上问题
而RAII在下一篇博客智能指针
会进行专门讲解

在这里插入图片描述
本篇博客完,感谢阅读🌹
如有错误之处可评论指出
博主会耐心听取每条意见

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

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

相关文章

企业出海的浪潮下,如何利用亚马逊云(AWS)更好地应对?

在全球化的浪潮下&#xff0c;越来越多的企业开始将目光投向国际市场。在这个数字化时代&#xff0c;云计算技术成为企业出海的必备利器之一。AWS云作为全球领先的云服务提供商&#xff0c;凭借其卓越的性能和完善的服务体系&#xff0c;成为众多企业出海的首选。 一、出海为什…

【Mybatis 与 Spring】事务相关汇总

之前分享的几篇文章可以一起看&#xff0c;形成一个体系 【Mybatis】一级缓存与二级缓存源码分析与自定义二级缓存 【Spring】Spring事务相关源码分析 【Mybatis】Mybatis数据源与事务源码分析 Spring与Mybaitis融合 SpringManagedTransaction&#xff1a; org.mybatis.spri…

小马搬运物品-第13届蓝桥杯省赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第89讲。 小马搬运物品&…

Redis 哨兵主备切换的数据丢失问题应该怎么解决?

引言&#xff1a;Redis作为一种高性能的内存数据库&#xff0c;广泛应用于分布式系统中。为了保证服务的高可用性&#xff0c;Redis提供了哨兵&#xff08;Sentinel&#xff09;机制&#xff0c;用于监控和管理Redis实例的自动故障恢复。然而&#xff0c;即使在哨兵的保护下&am…

计算机视觉中一些特殊的安装包

opencv-python Could not build wheels for opencv-python which use PEP 517 and cannot be installed 安装pip install opencv-python 输出 Collecting opencv-pythonDownloading https://files.pythonhosted.org/packages/77/f5/49f034f8d109efcf9b7e98fbc051878b83b2f02a1…

Windows宝塔面板部署ThinkPHP8.0创建Vue项目案例

安装ThinkPHP8.0 登录宝塔面板&#xff0c;创建一个站点。 输入composer代码&#xff0c;执行完成后自动创建TP目录 composer create-project topthink/think tp 网站目录设置为tp&#xff0c;运行目录设置为public 设置PHP版本为8.0以上&#xff0c;不然会出现下面的报错代…

【MotionCap】ImportError: cannot import name ‘packaging‘ from ‘pkg_resources‘

ImportError: cannot import name ‘packaging’ from ‘pkg_resources’ 降低setuptools的版本 参考大神:(ai-mocap) zhangbin@ubuntu-server:~/proj/04_mocap/third-party$ pip install -e neural_renderer

VBA 批量变换文件名

1. 页面布局 在“main”Sheet中按照下面的格式编辑。 2. 实现代码 Private wsMain As Worksheet Private intIdx As LongPrivate Sub getExcelBookList(strPath As String)Dim fso As ObjectDim objFile As ObjectDim objFolder As ObjectSet fso = CreateObject("Scrip…

为什么说BIM在机电安装行业是刚需?3D开发工具HOOPS如何促进BIM发展?

在建筑行业中&#xff0c;机电安装是一个复杂且精细的工程领域&#xff0c;它涉及到电气、管道、通风和控制系统等多个方面。随着建筑项目规模的不断扩大和复杂性的增加&#xff0c;传统的二维设计方法已经难以满足现代建筑的需求。正是在这种背景下&#xff0c;BIM技术应运而生…

第7章 Redis的噩梦:阻塞

文章目录 前言1 发现阻塞2.内在原因2.1API或数据结构使用不合理2.1.1如何发现慢查询2.1.2.如何发现大对象 2.2 CPU饱和2.3 持久化阻塞2.3.1fork阻塞2.3.2.AOF刷盘阻塞2.3.3.HugePage写操作阻塞 3 外在原因3.1CPU竞争3.2 内存交换 前言 Redis是典型的单线程架构&#xff0c;所有…

基于横纵向的混合联邦学习原理分析

近期陆续接触到关于混合联邦学习的概念&#xff0c;但基于横纵向的混合联邦实际的应用案例却几乎没有看到&#xff0c;普遍是一些实验性的课题&#xff0c;因此这一领域知识没有被很好普及。本篇文章的目的&#xff0c;主要是分析讨论关于横纵向混合联邦学习的业务场景、应用架…

【MySQL】InnoDB的存储结构

InnoDB的存储结构&#xff1a;每个表都会生成一个表空间文件&#xff0c;这个文件里面最小结构就是行&#xff0c;存储的真正的数据&#xff0c;一个页来管理若干行&#xff0c;一个区来管理若干页&#xff0c;一个区组来管理若干区。段并不是真正的物理存储结构&#xff0c;它…

Unity | Shader基础知识(番外:模型的制作流程)

目录 一、前言 二、模型的诞生 三、模型的表面 四、模型的贴图 五、上完材质的模型 六、材质的来源 七、作者的碎碎念 一、前言 up发现&#xff0c;初学程序&#xff0c;除非你是美术&#xff0c;模型出生&#xff0c;要不然对这些都是萌萌哒&#xff08;蒙蒙哒&#x…

基于51单片机心形LED流水灯电路原理图、PCB和源程序(SCH、PCB源文件)

资料下载地址&#xff1a;基于51单片机心形LED流水灯电路原理图、PCB和源程序&#xff08;SCH、PCB源文件&#xff09; 1、单片机心形LED流水灯功能说明&#xff1a; 单片机&#xff1a;无论是散件还是成品&#xff0c;单片机里面都烧录有LED 流水灯的程序&#xff0c;装上单片…

一文汇总VSCode多光标用法

光标的创建 按住alt&#xff0c;鼠标左键单击&#xff0c;在单击位置生成光标/删除光标 按住ctrlalt&#xff0c;单击↑/↓&#xff0c;在每行同一个位置&#xff08;若某一行较短&#xff0c;则在行尾&#xff09;生成光标&#xff0c;这个不会删除光标&#xff0c;只会在光标…

【语言模型】深入探索语言模型中的神经网络算法:原理、特点与应用

随着人工智能技术的飞速发展&#xff0c;神经网络算法在语言模型中的应用日益广泛&#xff0c;为自然语言处理领域带来了革命性的变革。本文将深入探讨当前语言模型中常用的几种神经网络算法&#xff0c;包括全连接神经网络、卷积神经网络、循环神经网络、长短期记忆网络、门控…

RabbitMQ(七)Shovel插件对比Federation插件

文章目录 Shovel和Federation的主要区别&#xff08;重点&#xff09;一、启用Shovel插件二、配置Shovel三、测试1、测试计划2、测试效果发布消息源节点目标节点 Shovel和Federation的主要区别&#xff08;重点&#xff09; • Shovel更简洁一些 • Federation更倾向于跨集群使…

Oracle中常用内置函数

一、字符串函数 CONCAT(s1, s2)&#xff1a;连接两个字符串s1和s2。 SELECT CONCAT(Hello, World) FROM DUAL-- 结果&#xff1a;Hello World --或者使用 || 操作符 SELECT Hello || World FROM DUAL -- 结果&#xff1a;Hello World INITCAP(s)&#xff1a;将字符串s…

深入理解计算机系统 CSAPP 8.4.2 fork函数

//fork.c #include <sys/types.h> #include <unistd.h> #include <stdio.h>int main() {pid_t fpid; //fpid表示fork函数返回的值int count 0;fpid fork();if (fpid < 0)printf("error in fork!");else if (fpid 0) {printf("\ni am th…

sql sever 存储过程不能请求https的解决方案

此错误的原因&#xff0c;通常是因为SQL Server默认不允许非加密的HTTP请求。为了解决这个问题&#xff0c;需要配置SQL Server允许非密码的https请求&#xff0c;或者使用密码的http请求。 下面是配置SQL Server允许非加密http请求 UsE [master] ;Go EXEC sp_configure Sh…