异常,这个十分陌生的名词;
试想一下,在我们之前写代码的过程中,程序运行出现了一些问题(就比如AVL树更新平衡因子的过程中,平衡因子出现了不可能的现象,这说明这个AVL树存在问题;)但是我们之前只是单纯的让程序终止,但是在以后的实践中,程序是一直运行的,所以我们不能直接将程序直接终止。
这是,我们应该做的是:对出现的问题进行处理,并且程序不能够终止
这里再看一下C语言传统的处理方式
assert这两种无论是哪一种处理方式都是返回错误码,部分情况下使用终止程序来处理非常严重的错误。
C++异常概念异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数直接或者间接的调用着者来处理这个错误
throw:当问题出现时,程序会抛出一个异常。使用throw来进行抛异常。try:try块中的代码标识将被激活的特定异常,通常后面跟着一个或者多个catch块。catch:在需要处理问题的地方,通过异常处理程序捕获异常,可以有多个catch进行捕获。throw抛出一个对象来引发异常,该对象的类型以及当前调用链就决定了应该由哪一个catch的处理代码来处理该异常。catch块是调用链中与该类对象类型匹配且离抛出异常位置最近的那一个。根据抛出对象的类型和内容,程序的抛出部分告知异常处理部分发生了什么错误。throw执行的时候,throw后面的语句将不再被执行;程序就从throw位置跳到与之匹配的catch块(catch可以是同一个函数内的,也可能是调用链中另一个函数的catch),简单来说有两种含义就是(1、沿着调用链的函数可能提前退出;2、`一旦程序开始执行异常处理程序,沿着调用链创还能的对象都要被销毁)。catch语句后销毁。类似与传值返回。这么多概念,看起来好麻烦,这里看一段代码理解一下
double Divide(int x, int y)
{
try
{
if (y == 0)
{
throw string("this divisor is zero");//这里直接使用临时对象
}
else
{
return (double)x / y;
}
}
catch (int y)
{
cout << "y:" << y << endl;
}
}
void func(int a)
{
try
{
if (a == 3)
{
throw string("a == 3");
}
int x, y;
cin >> x >> y;
Divide(x, y);
}
catch (double d)
{
cout << "d:" << d << endl;
}
}
int main()
{
int a;
cin >> a;
try {
func(a);
}
catch(string str)
{
cout << str << endl;
}
catch (char ch)
{
cout << ch << endl;
}
catch (...)
{
cout << "Unknown exception" << endl;
}
}这里补充:我们不能够用多个
catch完全匹配所以的异常类型,使用catch(...)来匹配所有的异常对象类型。
输入**3**,输出:
a == 3输入1 3 0,输出:
this divisor is zero简单理解,在我们平常练习写代码时,基本上用不到;在未来项目实践中才会有所使用。
catch语句,首先就要检查throw本身是否在try内部,如果在就查找匹配的catch语句,如果有就跳转。try/catch语句,或者当前函数内的catch类型不匹配,就会退出当前函数,在外层调用函数链中查找,整个过程叫做栈展开。main函数,依然没有找到匹配的catch语句,程序就会调用标准库的terminate函数终止程序。
非常量向常量的类型转换,数组向数组元素指针类型的转换,函数转换成函数指针,允许派生类向基类类型的转换(这个非常实用)。main函数,异常依旧没有匹配就会终止程序,不是有严重错误的情况下,我们不希望程序终止,所以我们一般在main函数中使用catch(...),它捕获所有异常,但是不知道异常错误是什么。对于异常重新抛出问题,大致意思就是: 有时
catch匹配到异常对象后,需要对错误进行分类,再对其中异常错误进行特殊的处理,其他的错误就要重新抛出;将问题抛出到外层调用链处理。 用法:就直接抛出throw;即可。
这里模拟一下发送信息这样的场景:
#include<thread>
class Exception
{
public:
Exception(const string& str, const int& id)
:_errmsg(str)
, _id(id)
{}
virtual string what() const
{
return _errmsg;
}
int getid() const
{
return _id;
}
protected:
string _errmsg;
int _id;
};
class HttpException :public Exception
{
public:
HttpException(const string& str, const int& id, const string type)
:Exception(str, id)
, string_type(type)
{}
virtual string what() const
{
string ret = "HttpException:";
ret += string_type;
ret += ":";
ret += _errmsg;
return ret;
}
private:
string string_type;
};
void _seedMsg(const string str)
{
if (rand() % 2 == 0)
{
throw HttpException("网络错误,请稍后重试", 101, "put");
}
else if (rand() % 7 == 0)
{
throw HttpException("你已经不是对方好友,发送失败", 102, "put");
}
else
{
cout << "发送成功" << endl;
}
}
void SeedMsg(const string& str)
{
for (int i = 0; i < 4; i++)
{
try
{
_seedMsg(str);
break;
}
catch (const Exception& e)
{
if (e.getid() == 101)
{
//网络问题
if (i == 3)
throw;
cout << "开始第" << i + 1 << "次尝试" << endl;
}
else
{
throw;
}
}
}
}
int main()
{
srand(time(0));
string str;
while (cin >> str)
{
try
{
SeedMsg(str);
}
catch (const Exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
}
return 0;
}在之前,我们程序是从上到下顺序执行的;但是如果抛出异常之后,程序就会跳转到异常匹配catch的位置,而抛异常throw后面的代码就不再执行;那这里就有一些问题:
我们先来看第一个问题
double Divide(int x, int y)
{
if (y == 0)
{
throw string("the y is zero");
}
return (double)x / double(y);
}
void test(int x, int y)
{
int* arr = new int[10];
try
{
Divide(x, y);
}
catch (const int& i)
{
cout << i << endl;
}
delete[] arr;
cout << "delete[] arr" << endl;
}
int main()
{
int x, y;
cin >> x >> y;
while (1)
{
try
{
test(x, y);
}
catch (const string& str)
{
cout << str << endl;
}
catch (...)
{
cout << "unknown exception" << endl;
}
cin >> x >> y;
}
return 0;
}这里在Divide函数中,如果y==0就会抛出异常,并且在该函数内没有捕获该异常,就继续将异常抛给外层调用的函数test,在test中new了一个int数组,但是没有捕获Divide函数抛出的异常,程序直接接跳到main函数当中去,就导致申请的空间资源没有被释放。
输入:(这里为了方便就写了死循环)
2 3
2 0输出:
delete[] arr
the y is zero这里确实造成了空间泄露的问题,那这个问题如何解决呢?
test函数中,调用Divide函数并且进行异常捕获,捕获之后释放空间资源再将异常抛出;void test(int x, int y)
{
int* arr = new int[10];
try
{
Divide(x, y);
}
catch (...)
{
delete[] arr;
cout << "delete[] arr" << endl;
throw;
}
}这里看似问题已经解决了,但是还存在新的问题;
如果这里申请了多个空间资源呢?
就比如
void test(int x, int y)
{
int* arr1 = new int[10];
int* arr2 = new int[10];
try
{
Divide(x, y);
}
catch (...)
{
delete[] arr1;
cout << "delete[] arr1" << endl;
delete[] arr2;
cout << "delete[] arr2" << endl;
throw;
}
}这样看起来貌似没有什么问题,但是如果第二个new抛异常了呢?
对于这个疑问,在后面
智能指针再来探讨。
对于第二个问题,在析构函数中尽量不出现抛异常。
如果析构函数需要释放十个,但是到第5个抛异常了,就需要捕获异常,导致后面资源释放不完全,造成资源泄露。
《Effctive C++》中也讲了,别让异常逃离析构函数。
https://legacy.cplusplus.com/reference/exception/exception/?kw=exception
C++标准库中定义了自己的一套异常继承体系库,基类是exception,所以我们在程序中只需要在主函数中捕获exception即可,要获取异常的具体信息直接调用what函数即可;而且what是虚函数。
