C++异常处理


在这里插入图片描述

1.C语言处理错误

  1. 终止程序,如assert,缺陷:用户难以接受。如发生内存错误,除0错误时就会终止程序。[DeBug可用Release无用]

在这里插入图片描述

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

在这里插入图片描述

总结: 对于小错误:返回错误码 对于严重的错误:终止程序

2.C++异常的概念

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

try: try 块中的代码被称为保护代码 ,执行保护代码可能会抛出异常

throw: 出现问题,程序抛异常。[关键字]

catch: 在要处理问题的地方捕获异常[关键字]

2.1普通情况

double Division(int a, int b)
{
	return ((double)a / (double)b);
}

void Func()
{
	int left, right;
	cin >> left >> right;
	cout << Division(left, right) << endl;
}

int main()
{
	Func();
	return 0;
}

在这里插入图片描述

inf是啥玩意儿???[infinite]

超出浮点数范围 即值很大或很小:1.0/0.0,-1.0/0.0,0.0+inf=inf;log(0);

有同学见过nan吗???这又是个啥???[Not a number]

sqrt(-1.0f): nan

上述inf和nan在使用浮点数时会出现

2.2使用异常处理

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.3由例子记性质

异常的抛出和匹配原则

  1. 异常是通过抛出对象而引发的,对象的类型决定了激活哪个catch
  2. 匹配的catch需要满足:类型匹配且离抛出异常位置最近

个例: 抛出派生类对象,使用基类捕获

  1. 特别注意:在这里插入图片描述抛出异常对象后,会生成一个异常对象的拷贝 这个拷贝的临时对象会在被catch以后销毁
  2. catch(…)可以捕获任意类型的异常,问题是不知道异常错误是什么

函数调用中 异常 栈展开

栈展开: 沿着调用链查找匹配的catch子句的过程

  1. 检查此throw是否在try块内部,是: 查找匹配的catch语句 – 调用catch
  2. 没有匹配的catch则退出当前函数栈,继续查找匹配的catch
  3. 如果到达main函数的栈依旧没有匹配 则终止程序。实际中最后都要加一个catch(…)捕获任意类型的异常,否则当有异常因为没有匹配上而没捕获,程序就会直接终止。
  4. 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行

2.4异常代码测试样例

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)
{
	if (b == 0)
	{
		//Exception err(1, "除0错误");
		//throw err;
		
		//throw Exception(1, "除0错误");
		
		throw 1;
	}
	else
	{
		return ((double)a / (double)b);
	}
}

void Func()
{
	int left, right;
	cin >> left >> right;

	try
	{
		cout << Division(left, right) << endl;
	}
	catch (char str)
	{
		cout << str << endl;
	}

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

int main()
{
	while (1)
	{
		try
		{
			Func();
		}
		catch (const Exception& e)
		{
			cout << e.GetMsg() << endl;
		}
		catch (...) 
		{
			cout << "未知异常" << endl;
		}
	}

	return 0;
}

3.模拟C++异常机制

3.1查阅文档

class exception 
{
public:
  exception () throw();
  exception (const exception&) throw();
  exception& operator= (const exception&) throw();
  virtual ~exception() throw();
  virtual const char* what() const throw();
}

3.2模拟异常体系

//基类 -- 异常基类
class Exception
{
public:
	//构造函数
	Exception(int errid, const string& errmsg)
		:_errid(errid)
		, _errmsg(errmsg)
	{
	
	}

	//基类 -- 获得错误信息的虚函数
	virtual string what() const
	{
		return _errmsg;
	}

	//获得错误码
	int GetErrid() const
	{
		return _errid;
	}

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

//数据库异常 -- 子类
class SqlException : public Exception
{
public:
	//构造函数
	SqlException(int errid, const string& msg, const string& sql)
		:Exception(errid, msg)
		, _sql(sql)
	{

	}

	//子类重写父类虚函数
	virtual string what() const
	{
		string msg = "SqlException:";
		msg = _errmsg + "->" + _sql;
		return msg;
	}

protected:
	string _sql;
};

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

	virtual string what() const
	{
		string msg = "CacheException:" + _errmsg;
		return msg;
	}
};

class HttpServerException : public Exception
{
public:
	HttpServerException(int id, const string& errmsg, const string& type)
		:Exception(id, errmsg)
		, _type(type)
	{
	
	}

	virtual string what() const
	{
		string msg = "HttpServerException:" + _errmsg + "->" + _type;
		return msg;
	}

private:
	const string _type;
};

//数据库测试
void SQLTest()
{
	srand(time(0));
	if (rand() % 8 == 0)
	{
		throw SqlException(100, "No permissions!", "Encountered special value!");
	}

	cout << "SQL call successful!" << endl;
}

//高速缓冲器测试
void CacheTest()
{
	srand(time(0));
	if (rand() % 6 == 0)
	{
		throw CacheException(1, "Insufficient permissions!");
	}
	else if (rand() % 7 == 0)
	{
		throw CacheException(2, "Data does not exist!");
	}

	SQLTest();
}

//网络服务器测试
void HttpServerTest()
{
	srand(time(0));
	if (rand() % 3 == 0)
	{
		throw HttpServerException(1,"The requested resource does not exist!", "get");
	}
	else if (rand() % 4 == 0)
	{
		throw HttpServerException(2, "Insufficient permissions!", "post");
	}
	else if (rand() % 5 == 0)
	{
		throw 1;
	}

	CacheTest();
}

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

		try
		{
			HttpServerTest();
		}
		catch (const Exception& e)  
		{
			//基类引用指向 基类/子类
			cout << e.what() << endl;
		}
		catch (...)
		{
			cout << "Unkown Exception" << endl;
		}
	}

	return 0;
}

在这里插入图片描述

4.异常的重新抛出

异常的重新抛出实际上是一种解决异常安全问题的一种办法 只不过这种办法不太好 是一种迫不得已的手段 更好的办法会在下一节<<智能指针>>中讲解 关注主啵不迷路~

4.0异常安全

  1. 构造函数完成对象的构造和初始化,不要在构造函数中抛出异常,可能导致对象不完整或没有完全初始化
  2. 析构函数主要完成资源的清理,不要在析构函数内抛出异常,可能导致资源泄漏(内 存泄漏、句柄未关闭等)
  3. 异常经常会导致资源泄漏问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁

4.1整体代码

//基类 -- 异常基类
class Exception
{
public:
	//构造函数
	Exception(int errid, const string& errmsg)
		:_errid(errid)
		, _errmsg(errmsg)
	{

	}

	//基类 -- 获得错误信息的虚函数
	virtual string what() const
	{
		return _errmsg;
	}

	//获得错误码
	int GetErrid() const
	{
		return _errid;
	}

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

//数据库异常 -- 子类
class SqlException : public Exception
{
public:
	//构造函数
	SqlException(int errid, const string& msg, const string& sql)
		:Exception(errid, msg)
		, _sql(sql)
	{

	}

	//子类重写父类虚函数
	virtual string what() const
	{
		string msg = "SqlException:";
		msg = _errmsg + "->" + _sql;
		return msg;
	}

protected:
	string _sql;
};

class CacheException : public Exception
{
public:
	CacheException(int id, const string& errmsg)
		:Exception(id, errmsg)
	{

	}

	virtual string what() const
	{
		string msg = "CacheException:" + _errmsg;
		return msg;
	}
};

class HttpServerException : public Exception
{
public:
	HttpServerException(int id, const string& errmsg, const string& type)
		:Exception(id, errmsg)
		, _type(type)
	{

	}

	virtual string what() const
	{
		string msg = "HttpServerException:" + _errmsg + "->" + _type;
		return msg;
	}

private:
	const string _type;
};

//数据库测试
void SQLTest()
{
	srand(time(0));
	if (rand() % 8 == 0)
	{
		throw SqlException(100, "No permissions!", "Encountered special value!");
	}

	cout << "SQL call successful!" << endl;
}

//高速缓冲器测试
void CacheTest()
{
	srand(time(0));
	if (rand() % 6 == 0)
	{
		throw CacheException(1, "Insufficient permissions!");
	}
	else if (rand() % 7 == 0)
	{
		throw CacheException(2, "Data does not exist!");
	}

	SQLTest();
}

//网络服务器测试
void HttpServerTest()
{
	srand(time(0));
	if (rand() % 3 == 0)
	{
		throw HttpServerException(1, "The requested resource does not exist!", "get");
	}
	else if (rand() % 4 == 0)
	{
		throw HttpServerException(2, "Insufficient permissions!", "post");
	}
	else if (rand() % 5 == 0)
	{
		throw 1;
	}

	CacheTest();
}

double Division(int a, int b) noexcept
{
	if (b == 0)
	{
		throw "Division by zero condition!";
	}

	return (double)a / (double)b;
}

void Func()
{
	// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
	// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
	// 重新抛出去。
	int* pointer = new int[10];

	int left, right;
	cin >> left >> right;

	try
	{
		cout << Division(left, right) << endl;
		HttpServerTest();
	}
	/*
	当抛异常时仍然想要执行catch块后的部分去释放堆区空间 以避免内存泄漏
	那么我们可以在这里加一个catch来匹配 而不让程序直接跳到main去匹配
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	*/
	
	//但是我们另一个更重要的目的是想要统一在main函数执行catch
	//那么我们可以在这里进行一个异常的重新抛出 在抛出之前 
	//我们可以先释放堆区空间 这样两个目的都达到[1.释放空间2.main匹配catch]

	//为什么要把异常统一放到main去匹配 ?
	//不同异常类型可能有许多工作要处理[假定所有的异常都要先输出错误信息并记录日志]
	// 下面情况还要做其他事情
	//比如遇到网络问题不是直接终止而是连续访问几次后若仍无网络再终止
	//如果我们在上一层函数就捕获了异常 那么就需要再当前函数记录日志并输出错误信息
	//为了增加代码复用性和提高泛型化 我们统一再main函数匹配catch 

	//因为我们不想在这里就进行catch匹配但是又需要释放空间
	//所以对于所有的异常我们都在此接收但只释放空间 重新抛异常
	//让main函数去处理
	catch (...) 
	{
		cout << "delete []" << pointer << endl;
		delete[] pointer;

		// catch到的是何异常类型就抛出何异常类型
		throw;   
	}
	//注意:这种办法是为了释放空间而不得已的做法 为了释放空间我们不得不搞出这样的处理方式
	//更好的解决办法在下一章: <<智能指针>> 中会详细讲解 关注博主不迷路哦~
	cout << "delete []" << pointer << endl;
	delete[] pointer;
}

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

4.2主要代码


void Func()
{
	// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
	// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
	// 重新抛出去。
	int* pointer = new int[10];

	int left, right;
	cin >> left >> right;

	try
	{
		cout << Division(left, right) << endl;
		HttpServerTest();
	}
	/*
	当抛异常时仍然想要执行catch块后的部分去释放堆区空间 以避免内存泄漏
	那么我们可以在这里加一个catch来匹配 而不让程序直接跳到main去匹配
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	*/
	
	//但是我们另一个更重要的目的是想要统一在main函数执行catch
	//那么我们可以在这里进行一个异常的重新抛出 在抛出之前 
	//我们可以先释放堆区空间 这样两个目的都达到[1.释放空间2.main匹配catch]

	//为什么要把异常统一放到main去匹配 ?
	//不同异常类型可能有许多工作要处理[假定所有的异常都要先输出错误信息并记录日志]
	// 下面情况还要做其他事情
	//比如遇到网络问题不是直接终止而是连续访问几次后若仍无网络再终止
	//如果我们在上一层函数就捕获了异常 那么就需要再当前函数记录日志并输出错误信息
	//为了增加代码复用性和提高泛型化 我们统一再main函数匹配catch 

	//因为我们不想在这里就进行catch匹配但是又需要释放空间
	//所以对于所有的异常我们都在此接收但只释放空间 重新抛异常
	//让main函数去处理
	catch (...) 
	{
		cout << "delete []" << pointer << endl;
		delete[] pointer;

		// catch到的是何异常类型就抛出何异常类型
		throw;   
	}
	//注意:这种办法是为了释放空间而不得已的做法 为了释放空间我们不得不搞出这样的处理方式
	//更好的解决办法在下一章: <<智能指针>> 中会详细讲解 关注博主不迷路哦~
	cout << "delete []" << pointer << endl;
	delete[] pointer;
}

5.C++异常规范

在这里插入图片描述

C++98

//当遇到A/B/C/D中某种类型的异常 -- 抛出
为了让函数使用者知道该函数可能抛出的异常有哪些 遇到异常E的类型也会抛出
void fun() throw(A,B,C,D);
//当遇到bad_alloc的异常 -- 抛出
void* operator new (size_t size) throw (bad_alloc);
//不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();
//可以抛任何类型异常
void fun();

需要注意的是 上述声明 是否会抛出异常/遇到何种类型的异常抛出 不是强制的 就是说加不加都行
那人们为了省事当然不加 于是这个规范形同虚设

C++11

//C++98的依然支持
class exception
 {
public:
  exception () noexcept;//明确不抛异常 -- 如果一个会抛出异常的函数加了noexcept会报错
  //实际上有的编译器对于这一块是有点含糊的 比如main函数调用A函数 A函数调用B函数 B函数后加noexcept如果会抛异常则报错 但是只在A后面加noexcept 虽然有异常抛出但却可以运行通过[即简介抛出异常的检测不到]  那么对于这一块 既然C++设计的没有强制我们使用  那就别加这种东西 加了反而比较麻烦
  exception (const exception&) noexcept;
  exception& operator= (const exception&) noexcept;
  virtual ~exception();//可能会抛异常 不加
  virtual const char* what() const noexcept;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿猿收手吧!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值