C++学习记录——이십구 异常

文章目录

  • 1、异常概念
  • 2、实际用法
  • 3、C++标准库的异常体系
  • 4、重新抛出异常
  • 5、优缺点


1、异常概念

C语言处理错误有assert,返回错误码来处理错误的方式,不过release模式下assert无效,错误码需要程序员自己去查看是什么错误。

C++认为应当能给到程序员一个更明确的错误,所以就出现了异常处理。当出现错误时,C++会抛一个对象,里面包含错误信息。

异常有3个关键字,throw抛出异常,try和catch捕获异常。

#include 
using namespace std;

double Division(int a, int b)
{
	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();
	return 0;
}

如果没有异常,那就正常运行,如果有异常,必须被捕获,否则就只会终止程序并弹出错误窗口。捕获的位置不一定只包含有错误的代码块。捕获一般在外部捕获。

int main()//也可以写在Func函数里。不过通常写在最外面
{
	try
	{
		Func();
	}
	catch (const char* str)
	{
		cout << str << endl;
	}
	return 0;
}

当出现异常后,throw会直接跳到catch处。catch只会捕获try里的代码。如果Func和main都有捕获,那么出异常会去哪里?会跳到Func函数里,它会跳到离throw最近的那个位置,然后继续执行跳到的位置的后面的代码,之后所有的捕获就不用再捕获了。

异常出现后先在异常所在的栈帧里查找catch,没有就结束这个栈帧,然后到上一层栈帧里去查找catch,还是没有结束这个栈帧,继续向上找。

捕获时参数也得匹配上类型,比如Func和main都有捕获的话,throw就会到const char*类型的那个catch处,会找匹配异常的catch,如果全都没有,那就终止程序,弹出错误窗口,因为没有捕获的,相当于只有throw,没有catch,那就直接报错。

2、实际用法

写的时候不能像上面那样粗糙,要写一个类来捕获。

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

为什么要这么写?实际的程序中,出现异常并不是一定要立即报出来的,比如发消息,如果网络不好,发送失败,那就会重试好几次,直到到达阈值,如果还是失败,那就返回异常。

写一个伪代码

void TrySendMsg()
{
	try
	{
		SendMsg();
	}
	catch(const Exception& e)
	{
		if (e.getErrid() == 3)//假设的一个错误码数字
		{
			//重试
		}
		else
		{
			//记录日志,界面展示错误信息
		}
	}
}

回到我们的代码就这样写

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;
	}
	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 << "Func()" << endl;
}

int main()
{
	try
	{
		Func();
	}
	catch (const Exception& e)
	{
		//看个人需求来处理
		cout << e.GetMsg() << endl;
	}
	return 0;
}

抛出的是局部对象err,编译器会拷贝一份,将拷贝的传给下面的e,catch完后拷贝的那份就结束生命周期了。

如果想捕获自己不知道什么类型的异常,那就这样写

	try
	{
		Func();
	}
	catch (const Exception& e)
	{
		//看个人需求来处理
		cout << e.GetMsg() << endl;
	}
	catch(...)
	{
		cout << "未知异常" << endl;
	}

三个点就可以接收所有。try和catch可以套上循环。未知异常是一种底线,防止错误没被捕获到。

如果异常类型很多,不同的抛异常需求,还有很多未知异常,这如何处理?可以抛出派生类对象,用基类捕获,派生类转换到基类是天然的转换,是用切割做到的。Exception是基类,带着基础信息,其它程序员添加自己的需求来作为派生类,这里也可以用多态。

写一个模拟用继承和多态来接收类的代码

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

	const string& what() const
	{
		return _errmsg;
	}

	int GetErrid() const
	{
		return _errid;
	}
protected:
	int _errid;//错误码
	string _errmsg;//错误描述
};

//每个子类都写一个what,形成多态
class SException : public Exception
{
public:
	SException(const string& msg, int errid, const string& s)
		:Exception(errid, msg)
		, _s(s)
	{}

	virtual string what() const
	{
		string str = "SException:";
		str += _errmsg;
		str += "->";
		str += _s;
		return str;
	}
protected:
	string _s;
};

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

	virtual string what() const
	{
		string str = "CException:";
		str += _errmsg;
		return str;
	}
};

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

	virtual string what() const
	{
		string str = "SException:";
		str += _errmsg;
		str += _type;
		str += _errmsg;
		return str;
	}
private:
	const string _type;
};

void SMgr()
{
	srand(time(0));
	if (rand() % 7 == 0)
	{
		throw SException("权限不足", 100, "select * from name = '张三'");
	}
	cout << "调用成功" << endl;
}

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

void HServer()
{
	//模拟服务出错
	srand(time(0));
	if (rand() % 3 == 0)
	{
		throw HSException("请求资源不存在", 100, "get");
	}
	else if (rand() % 4 == 0)
	{
		throw HSException("权限不足", 101, "post");
	}
	CMgr();
}


int main()
{
	while (1)
	{
		this_thread::sleep_for(chrono::seconds(1));
		try
		{
			HServer();
		}
		catch (const Exception& e)
		{
			cout << e.what() << endl;
		}
		catch (...)
		{
			cout << "Unkown Exception" << endl;
		}
	}
	return 0;
}

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

库中的是exception。

C++学习记录——이십구 异常_第1张图片

C++学习记录——이십구 异常_第2张图片
每个异常都会找到与它最匹配的那个处理方式,直到每个异常都处理完,如果找遍了所有的也还没有处理,那就弹出错误窗口。

C++98的标准里,如果在声明一个函数后,写上throw(),就像之前写const的位置,那么这个throw()就表明这个函数不抛异常。如果throw()括号里写上异常类型,那就表明这个函数只抛这些类型的异常。实际上这个规范通常不用。

C++11中有新规则,它仍然兼容98。一个函数明确不抛异常的话,就在原先写throw()的位置写上noexcept,比如void Func() noexcept,但是还是要抛的话,那就会出错,执行不了;可能抛异常,那就什么都不写。

最好不要在构造函数抛异常,可能导致没有初始化完成。

像new/malloc/fopen/lock时不要抛异常,它们需要delete/free/fclose/unlock,因为会导致内存泄漏,文件未关闭、死锁等问题。这方面可以用智能指针来更好地解决。

4、重新抛出异常

double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (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里可以看到如果发生除0错误抛出异常,下面的array就没有得到释放。所以捕获异常后应当不处理异常,异常交给外面处理,这里捕获了再重新抛出去。

void Func()
{
	int* array = new int[10];
	int len, time;
	cin >> len >> time;
	try
	{
		cout << Division(len, time) << endl;
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	cout << "delete []" << array << endl;
	delete[] array;
}

不过我们仍然可以交给main来处理,Func那里抓到异常后,先执行后面的,然后再次throw,就是重新抛出异常。

void Func()
{
	int* array = new int[10];
	int len, time;
	cin >> len >> time;
	try
	{
		cout << Division(len, time) << endl;
	}
	catch (const char* errmsg)
	{
		cout << "delete []" << array << endl;
		delete[] array;
		throw errmsg;
	}
}

还应当在main里加上捕获未知异常。如果Func里有调用多个不同的函数,那么可以写上捕获所有的catch,也就是catch(…)。不过捕获异常最好全都在一起,比如main里,方便记录。

5、优缺点

优点:
1、相比错误码,能更清晰地展示各种错误信息,附带各种需要的数据
2、错误码是层层返回的
3、第三方库的异常需要捕捉
4、部分函数使用异常更好处理,比如构造函数没有返回值,越界等问题。、

缺点:
1、异常容易乱跳,导致程序的执行流乱跳,调试时会直接跳到catch处
2、异常容易导致内存泄漏、死锁等安全问题,C++也没有垃圾回收机制
3、C++标准库的异常体系不怎么好,一般都使用自定义的异常体系
4、异常需要规范起来,否则捕获就很不容易。抛出的异常类型都继承一个基类,以及无论抛什么异常都用noexcept等来规范起来

本篇gitee

结束。

你可能感兴趣的:(C++学习,c++,学习)