C++的异常捕获

news2024/11/23 15:37:09

目录

C语言的异常处理方式

C++的异常处理方式

异常的抛出与捕获

抛出与捕获原则

自定义异常体系

异常安全

异常规范

异常的优缺点

优点

缺点


C语言的异常处理方式

1、终止程序

常见形式:assert

缺陷:太过强硬,如果发生内存错误,或者除0语法错误等就会直接终止程序

2、返回错误码

缺陷:需要程序员自己去查找对应的错误,如系统的很多库的接口函数都是通过把错误码放到errno中,表示错误

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

C++的异常处理方式

基本概念:抛异常是编程中的一种机制(c++ / python等),用于在程序遇到错误或异常情况时中断正常的程序流,并将控制权转移到预定的异常处理逻辑中。异常处理使得程序能够优雅地处理错误,而不是直接崩溃(异常是一种运行时错误,可以中断程序的正常流程,常见的异常包括除零错误、文件未找到、网络连接失败等)

  • 抛出(Throwing): 使用 throw 关键字将异常对象传递出去
  • 捕获(Catching): 使用 try...catch 块来捕获并处理异常
try{// 可能会出现异常的代码 
}

//根据类型抛出异常的类型进行捕获
catch( ExceptionName e1 ){//处理方式}
catch( ExceptionName e2 ){//处理方式}
....
  • 传播(Propagation):如果异常未被捕获,它会向调用栈的上传递,直到找到一个处理器,如果找不到一个处理器就会使程序终止
  • 异常对象:该对象包含了错误的详细信息以及上下文,用于捕获和处理异常

优点:

  • 提高代码的健壮性,使得程序能够优雅地处理异常情况
  • 提供了错误传播和处理的标准机制

缺点:

  • 可能会使代码变得复杂,其中的栈展开机制会打乱正常的执行流

  • break、continue、return等虽然也会破坏正常执行流,但并会跨越多个函数  
  • 不当的异常处理可能会掩盖程序中的逻辑错误

异常的抛出与捕获

抛出与捕获原则

#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);//否则返回a / b后的结果
}

void fxx()
{
	int i = 0;
	cin >> i;
	if (i % 2 == 0)//出现偶数时抛异常
	{
		throw 1;
	}
}

void Func()
{
	int a, b;
	cin >> a >> b;
	cout << Division(a, b) << endl;
	try
	{
		fxx();//可能出异常的代码
	}
	catch (int x)
	{
		cout <<__LINE__<<"捕获异常:" << x << endl;
	}

	cout << "=====================" << endl;

}

int main()
{
	try 
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (int x)
	{
		cout << __LINE__ <<"捕获异常:"<< x << endl;
	}

	cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~~" << endl;

	return 0;
}

1、抛出异常的对象的类型决定了会调用哪个catch中的异常处理代码

2、进行异常处理的代码应该是离抛异常位置最近的那一个(就近原则)

3、找到匹配的catch子句并处理后,会继续执行catch子句后的非try...catch块中的内容。不会执行原函数中的内容了

4、抛出异常对象后,会生成一个拷贝该异常对象得到的临时对象(右值),这个临时对象会在被catch后销毁,可以调用移动构造从而省去拷贝构造这一过程

void fxx()
{
	int i = 0;
	cin >> i;
	if (i % 2 == 0)
	{
		string s("出现偶数");
		throw s;
	}
}

5、catch(...)可以捕获任意类型的异常对象,用于捕获未知异常对象的情况,是异常捕获的最后一道防线必须加上,防止出现在规定的异常对象外的情况而捕捉不到的情况

6、实际上,抛出对象的类型与捕获的类型并不一定都是匹配的,大多数情况下会抛出派生类对象,使用基类捕获

自定义异常体系

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

#include <iostream>
#include <windows.h>

using namespace std;

// 服务器开发中通常使用的异常继承体系
class Exception
{
public:
	Exception(const string& errmsg, int id)
		:_errmsg(errmsg)
		, _id(id)
	{}

	virtual string what() const
	{
		return _errmsg;
	}

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

class A
{
public:
	A()
	{
		cout << "A()" << endl;

		_ptr1 = new int;
		_ptr2 = new int;
	}

	~A()
	{
		cout << "~A()" << endl;

		delete _ptr1;
		delete _ptr2;
	}

private:
	int* _ptr1;
	int* _ptr2;
};


void SQLMgr()
{
	srand(time(0));
	if (rand() % 7 == 0)
	{
		throw SqlException("权限不足", 100, "select * from name = '张三'");
	}

	cout << "!!!!!!!!!!!!!!!!!!!!!!!!运行成功" << endl;
}

void CacheMgr()
{
	srand(time(0));
	if (rand() % 5 == 0)
	{
		throw CacheException("权限不足", 100);
	}
	else if (rand() % 6 == 0)
	{
		throw CacheException("数据不存在", 101);
	}
	SQLMgr();
}

void HttpServer()
{
	A aa;
	srand(time(0));
	if (rand() % 3 == 0)
	{
		throw HttpServerException("请求资源不存在", 100, "get");
	}
	else if (rand() % 4 == 0)
	{
		throw HttpServerException("权限不足", 101, "post");
	}

	CacheMgr();
}

int main()
{
	srand(time(0));

	while (1)
	{
		Sleep(1000);

		try 
		{
			HttpServer(); // io
		}
		catch (const Exception& e) // 这里捕获父类对象就可以
		{
			cout << e.what() << endl;
		}
		catch (...)
		{
			cout << "Unkown Exception" << endl;//未知捕获
		}
	}

	return 0;
}

  • 基类 Exception:

    • 用于处理所有异常的基类
    • 包含错误消息 (_errmsg) 和错误编号 (_id)
    • 提供 what() 方法,返回错误描述
  • 派生类 SqlException:

    • 专门处理与SQL相关的异常
    • 添加了 SQL 语句的字段 _sql
    • 重写 what() 方法以提供包含 SQL 语句的错误信息
  • 派生类 CacheException:

    • 处理缓存相关的异常
    • what() 方法返回基于缓存错误的信息
  • 派生类 HttpServerException:

    • 用于处理HTTP服务器的异常
    • 包括一个请求类型字段 _type
    • what() 方法返回包括请求类型的详细错误信息

结论:出异常时都是抛出相应的派生类对象,且SQLMgr、CacheMgr、HttpServer三个函数都没有解决抛异常的try...catch块,栈展开后都会回到main函数进行异常处理(描述的可能有误,建议调试查看)

异常安全

1、最好不要在构造函数中抛出异常,否则可能导致对象不完整或者没有完全初始化

2、最好不要在析构函数中抛出异常,析构函数主要用于资源的清理,抛异常可能导致资源泄漏

//可能出问题的代码
double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
		throw "Division by zero condition!";
	else
		return ((double)a / (double)b);
}

class A
{
public:
	A()
	{
		cout << "A()" << endl;
		_ptr1 = new int;
        //在构造函数中设计抛异常
		int x, y;
		cin >> x >> y;
		Division(x, y);//如果此时抛异常了,那么就会进行异常处理,然后直接执行catch子句后的内容,_ptr2未正常初始化1

		_ptr2 = new int;
	}

	~A()
	{
		cout << "~A()" << endl;
		delete _ptr1;
        //在析构函数中设计抛异常
		int x, y;
		cin >> x >> y;
		Division(x, y);//如果此时抛异常了,那么就会进行异常处理,然后直接执行catch子句后的内容,_ptr2未释放

		delete _ptr2;
	}

private:
	int* _ptr1;
	int* _ptr2;
};

3、在C++异常中经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RALL来解决上述问题

异常规范

基本概念:C++98时对异常抛出有一些建议性的规范,但这并不是强制的,当然实际上这一规范也没能解决使用异常时会出现的一些问题:

  1. 函数后接throw(),表示此函数不会抛异常
  2. 函数后接throw(?),表示此函数可以抛出?类型的异常
  3. 函数后接throw(? / *...),表示此函数可以抛出?或*等类型的异常

// 这里表示这个函数会抛出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新增的noexcpt关键字,表示也用于表示一个函数不会抛异常

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

结论:可能会抛异常时,函数后什么都不加,抛异常的操作在函数内直接写就行,如果缺定该函数不会抛异常,那么就在该函数后加noexcept关键字

异常的优缺点

优点

1、异常对象的处理方式定义好后,相比于错误码可以更加清晰准确的显示出错误的各种信息,甚至可以包含堆栈的调用信息,可以帮助我们更快的定位程序的bug

class Exception
{
public:
	Exception(const string& errmsg, int id)
		:_errmsg(errmsg)
		, _id(id)
	{}

	virtual string what() const
	{
		return _errmsg;
	}

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

2、错误码需要层层返回错误,最外层才能拿到错误,异常可以直接跳转,不用层层处理

3、很多第三方库都支持异常,所以我们应该也要去使用

4、部分函数使用异常更好处理,比如构造函数没有返回值,不便使用错误码,比如T& operator这样的函数,如果pos越界了之呢使用异常或终止程序进行处理,没法通过返回值表示错误

缺点

1、异常不仅会导致程序的执行流乱跳,且运行时出错导致的抛异常也会使得程序看起来很混乱,这导致我们跟踪调试以及分析程序时,比较困难

2、C++没有垃圾回收机制,资源需要自己管理,有了异常很容易内存泄漏、死锁等异常安全问题,这需要使用RAII机制来处理资源的管理问题,学习成本高

3、C+标准库的异常体系定义的不好,导致各公司各自定义自己的异常体系,十分混乱

~over~

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

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

相关文章

文心一言 VS 讯飞星火 VS chatgpt (282)-- 算法导论20.4 3题

三、在 CONNECTED-COMPONENTS 作用于一个有 k 个连通分量的无向图 G(V&#xff0c;E) 的过程中&#xff0c;FIND-SET 需要调用多少次&#xff1f; UNION 需要调用多少次&#xff1f;用 |V| 、 |E| 和 k 来表示你的答案。如果要写代码&#xff0c;请用go语言。 文心一言&#x…

【Java】过滤器/拦截器

文章目录 两者区别request链路全过程 在实际开发中&#xff0c;过滤器和拦截器都是经常使用的技术&#xff0c;但一被提及到其区别时&#xff0c;整个人就愣住了&#xff0c;好像没有认真地对两者进行区别和总结&#xff0c;这两者之间也确实很容易混淆&#xff0c;因此结合了很…

C++ 54 之 继承中同名的静态成员处理

#include <iostream> using namespace std;// 父类 class Base07{ public:static int m_a; // 静态成员&#xff0c;类内定义static void fun(){cout << "Base中的fun"<< endl;}static void fun(int a){cout << "Base中的fun(int a)&qu…

53. QT插件开发--插件(动态库so)的调用与加载

1. 说明 在使用QT进行插件库的开发之后,还需要将这个插件库程序生成的so动态链接库加载到主程序框架中进行使用,才能达到主程序的模块化开发的效果。在前一篇文章插件创建中介绍了如何在QT中开发插件库,并提供外部接口调用。本篇博客的主要作用是模拟在主程序框架中加载动态…

EasyRecovery2024数据恢复神器#电脑必备良品

EasyRecovery数据恢复软件&#xff0c;让你的数据重见天日&#xff01; 大家好&#xff01;今天我要给大家种草一个非常实用的软件——EasyRecovery数据恢复软件&#xff01;你是不是也曾经遇到过不小心删除了重要的文件&#xff0c;或者电脑突然崩溃导致数据丢失的尴尬情况呢&…

element-ui input输入框和多行文字输入框字体不一样

页面中未作样式修改&#xff0c;但是在项目中使用element-ui input输入框和多行文字输入框字体不一样&#xff0c;如下图所示&#xff1a; 这是因为字体不一致引起的&#xff0c;如果想要为Element UI的输入框设置特定的字体&#xff0c;你可以在你的样式表中添加以下CSS代码…

快速UDP网络连接之QUIC协议介绍

文章目录 一、QUIC协议历史1.1 问题&#xff1a;QUIC为什么在应用层实现1.2 QUIC协议相关术语1.3 QUIC和TCP对比1.4 QUIC报文格式1.4.1 QUIC报文格式-Stream帧11.4.2 QUIC报文格式-Stream帧2 二、QUIC的特点2.1 连接建立低时延&#xff0c;2.2 多路复用流复用-HTTP1.1流复用-HT…

SpringBoot整合H2数据库并将其打包成jar包、转换成exe文件二(补充)

SpringBoot整合H2数据库并将其打包成jar包、转换成exe文件二&#xff08;补充&#xff09; 如果你想在cmd命令窗口内看到程序运行&#xff0c;即点开弹出运行窗口&#xff0c;关闭时exe自动关闭。 需要再launch4j上进行如下操作&#xff1a; 这样转换好的exe就可以有控制台了…

springboot + Vue前后端项目(第十六记)

项目实战第十六记 写在前面1 第一个bug1.1 完整的Role.vue 2 第二个bug2.1 修改路由router下面的index.js 总结写在最后 写在前面 发现bug&#xff0c;修复bug 1 第一个bug 分配菜单时未加入父id&#xff0c;导致分配菜单失效 <!-- :check-strictly"true" 默…

人工智能对零售业的影响

机器人、人工智能相关领域 news/events &#xff08;专栏目录&#xff09; 本文目录 一、人工智能如何改变零售格局二、利用人工智能实现购物体验自动化三、利用人工智能改善库存管理四、通过人工智能解决方案增强客户服务五、利用人工智能分析消费者行为六、利用 AI 打造个性化…

C++前期概念(重)

目录 命名空间 命名空间定义 1. 正常的命名空间定义 2. 命名空间可以嵌套 3.头文件中的合并 命名空间使用 命名空间的使用有三种方式&#xff1a; 1:加命名空间名称及作用域限定符&#xff08;::&#xff09; 2:用using将命名空间中某个成员引入 3:使用using namespa…

TCP协议报头详解

目录 前言 TCP特点 TCP报头 1.源端口和目的端口 2.序号 3.确认号 4.数据偏移 5.保留 6.控制位 ① 紧急URG&#xff08;URGent&#xff09; ② 确认ACK&#xff08;ACKnowledgment&#xff09; ③ 推送PSH&#xff08;PuSH&#xff09; ④复位RST&#xff08;ReSeT&…

【数据结构】初识集合深入剖析顺序表(Arraylist)

【数据结构】初识集合&深入剖析顺序表&#xff08;Arraylist&#xff09; 集合体系结构集合的遍历迭代器增强for遍历lambda表达式 List接口中的增删查改List的5种遍历ArrayList详解ArrayList的创建ArrayList的增删查改ArrayList的遍历ArrayList的底层原理 &#x1f680;所属…

UnityAPI学习之 播放游戏音频的类(AudioSource)

播放游戏音频的类&#xff08;AudioSource&#xff09; using System.Collections; using System.Collections.Generic; using UnityEngine;public class NO17AudioSource : MonoBehaviour {private AudioSource audioSource;//音频组件public AudioClip clip;//音频文件public…

预编译、函数变量提升

函数声明会覆盖变量的声明&#xff0c;也就是会提升到最前面。 形参传进来相当于变量声明&#xff0c;所以当有函数声明时&#xff0c;会被覆盖。

情绪管理:大我则定,小我则乱(王阳明)

学了很多知识&#xff0c;却还是感物易动&#xff1f;如何让心回归中正&#xff1f;王阳明一言以蔽之&#xff1a; —— 大我&#xff0c;大我则定&#xff0c;小我则乱 保持心静的方法&#xff1a;有大爱&#xff0c;为大局着想

Spark-Shuffle阶段优化-Bypass机制详解

Spark概述 Spark-Shuffle阶段优化-Bypass机制详解 Spark的Bypass机制是一种特定情况下的优化策略&#xff0c;目的是减少Shuffle过程中不必要的排序开销&#xff0c;从而提升性能。当Shuffle分区数较少且数据量不大时&#xff0c;Bypass机制可以显著加快Shuffle速度。 1.什么…

使用 Nginx 和 SSL 访问 Python Flask 应用的教程

在本教程中&#xff0c;我们将介绍如何使用 Nginx 和 SSL 来访问 Python Flask 应用。通过这种方式&#xff0c;你可以在提高安全性的同时&#xff0c;也能利用 Nginx 的反向代理功能来优化应用的性能和稳定性。 环境准备 在开始之前&#xff0c;请确保你的系统已经安装了以…

准备离职了 电脑怎么清理?离职最干净的电脑清理办法

准备离职了 电脑怎么清理&#xff1f;离职最干净的电脑清理办法 人在江湖身不由己&#xff0c;离职这个事情&#xff0c;所有人都要面对。无论是出于个人发展、工作环境、薪资待遇还是其他原因&#xff0c;离职都是人生和职业道路上的一种常态。离职是一个残酷的事实&#xff…

【每日刷题】Day66

【每日刷题】Day66 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 小乐乐改数字_牛客题霸_牛客网 (nowcoder.com) 2. 牛牛的递增之旅_牛客题霸_牛客网 (nowcoder.com)…