异常,当一个函数发现自己无法处理的错误时抛出异常,让函数的调用者直接或间接的处理这个问题。
异常的抛出和捕获
1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个处理代码。
2. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
3. 抛出异常后会释放局部存储对象,所以被抛出的对象也就还给系统了,throw表达式会初始化一个抛出特殊的异常对象副本(匿名对象),异常对象由编译管理,异常对象在传给对应的catch处理之后撤销。
try
{
… //可能出错产生异常的代码
throw(type);
}catch(type1)
{
… //对应类型的异常处理代码
}catch(type2)
{
… //对应类型的异常处理代码
}
栈展开
抛出异常的时候,将暂停当前函数的执行,开始查找对应的匹配catch子句。
首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。
如果有匹配的,则处理。没有则退出当前函数栈,继续在调用函数的栈中进行查找。
不断重复上述过程。若到达main函数的栈,依旧没有匹配的,则终止程序。
上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。
找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<string>
class Exception
{
public:
Exception(int id, const char *msg = "")
:_errid(id)
, _errmsg(msg)
{}
protected:
int _errid;
string _errmsg;
};
void F2()
{
char* p1 = (char*)malloc(0x7fffffff);
if (p1 == 0)
{
string msg("申请内存失败");
throw msg;
}
cout << "F2()" << endl;
}
void F1()
{
F2();
cout << "F1()" << endl;//注意这句
}
int main()
{
try
{
F1();
}
catch (string msg)
{
cout << msg << endl;
}
return 0;
}
上面的程序在F2()中抛出异常,先在F2()中找是否有捕获语句,没有就跳到调用F2()的F1()中找,还没有就跳到main函数中找,在main函数中找到对应catch语句并处理,主要在F1()中调用F2()后还有一句输出F1()并没有执行,因为捕获异常后直接从catch语句后执行,输出F1()的语句被跳过。
若没有对应的捕获语句会发生什么事?
我们改一下最后的捕获语句,将类型由string类改为int类,使类型不匹配
catch (int msg)
{
cout <<msg<< endl;
}
可以看到程序出现BUG,程序挂了。
异常捕获的匹配规则
异常对象的类型与catch说明符的类型必须完全匹配。只有以下几种情况例外
1. 允许从非const对象到const的转换。
2. 允许从派生类型到基类类型的转换。
3. 将数组转换为指向数组类型的指针,将函数转换为指向函数类型的指针。
异常的重新抛出
有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<string>
class Exception
{
public:
Exception(int id, const char *msg = "")
:_errid(id)
, _errmsg(msg)
{}
protected:
int _errid;
string _errmsg;
};
void F2()
{
char* p1 = (char*)malloc(0x7fffffff);
if (p1 == 0)
{
string msg("申请内存失败");
throw msg;
}
cout << "F2()" << endl;
}
void F1()
{
try
{
F2();
}
catch (string msg)//第一次捕获
{
cout << "F1()" << endl;
throw;//抛出
}
}
int main()
{
try
{
F1();
}
catch (string msg)//第二次捕获
{
cout << msg << endl;
}
return 0;
}
可以看到这次的运行结果输出了F1(),证明在F1()中捕获了异常又重新抛出。
异常规范
在函数声明之后,列出该函数可能抛出异常类型,并保证该函数不会抛出其他类型的异常。
1、成员函数在类内声明和类外定义两处必须有相同的异常规范。
2、函数抛出一个没有被列在它异常规范中的异常时(且函数中抛出异常没有在函数内部进行处理),
系统调用C++标准库中定义的函数unexpected().
3、如果异常规范为throw(),则表示不得抛出任何异常,该函数不用放在try块中。
4、派生类的虚函数的异常规范必须与基类虚函数的异常规范一样或更严格(是基类虚函数的异常的子集)。因为:派生类的虚函数被指向基类类型的指针调用时,保证不会违背基类成员函数的异常规范。
异常与构造函数&析构函数:
1. 构造函数完成对象的构造和初始化,需要保证不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化。
2. 析构函数主要完成资源的清理,需要保证不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等)