异常处理
C++语言本身或标准程序库所抛出的所有异常,都派生自基类exception。这是其他数个标准异常类别的基类,它们共同构成一个类体系:
传统错误处理办法
1. 终止程序。(如段错误等)
2. 返回错误码。
3. 返回合法值,让程序处于某种⾮法的状态。(误导人,不可用)
4. 调一个预先设置的出现错误时调⽤的函数。(回调函数)
5.使用abort()或exit()暴力终止程序
6.使用goto语句
7. set jmp()和long jmp()组合(set jump必须先调用,在异常位置通过调用long jmp以恢复先前被保存的程序执行点,否则将导致不可预测的结果,甚至程序崩溃。在调用setjmp的函数返回之前调动longjmp,否则结果不可预料)
异常对象的类型与catch说明符的类型必须完全匹配。只有以下三种情况例除外
1. 允许从const对象到const的转换。
2. 允许从派类型到基类类型的转换。
3. 将数组转换为指向数组类型的指针,将函数转换为指向函数类型的指针。
先简单的看一个例子:
double fun(int x,int y)
{
if (y==0)
{
throw 1;
}
return x/y;
}
int main()
{
try
{
cout<<"4/4="<<fun(4,4)<<endl;
cout<<"4/0="<<fun(4,0)<<endl;
}
catch (...)
{
cout<<"y!=0"<<endl;
}
return 0;
}
这里catch后圆括号中的...指的是捕获所有异常。
抛出异常后会释放局部存储对象,所以被抛出的对象也就还给系统了,throw表达式会初始化这个抛出特殊的异常对象副本(匿名对象),异常对象由编译管理,异常对象在传给对应的catch处理之后撤销。栈展开抛出异常的时候,将暂停当前函数的执行,才开始查找对应的匹配catch语句。先检查throw是否在try块内部,如果是再查找匹配的catch语句。
如果有匹配的,则处理。没有则退出当前函数栈,继续在调函数的栈中进行查找不断重复上述过程。若到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的catch句的过程称为栈展开。找到匹配的catch语句并处理以后,会继续沿着catch语句继续执行。
在栈展开的时候,会退出某个函数,释放当前内存并撤销局部对象,这时会调用对象的析构函数,如果析构函数抛出异常,将会调用标准库terminate函数,强制整个程序非正常退出。所以析构函数应该从不抛出异常。
异常是通过抛出对象引发的,该对象的类型决定了应该激活哪个处理代码。
在查找匹配的catch期间,找到的catch不必是与异常最匹配的那个catch,相反,将选中第一个找到的可以处理该异常的catch。因此,在catch子句列表中,最特殊的catch必须最先出现,否则没有执行的机会。
实例如下:
int FunTest1()
{
int error1=1;
throw error1;
}
char FunTest2()
{
char error2='2';
throw error2;
}
int main()
{
try
{
FunTest1();
FunTest2();
}
catch (int &error1)
{
cout<<"int "<<endl;
}
catch (char &error1)
{
cout<<"char"<<endl;
}
return 0;
}
进入catch的时候,用异常对象初始化catch的形参。因为基类的异常说明符可以捕获派生类的异常对象。如果异常对象是引用,则可以使用多态,调用基类的virtual将执行派生类的覆盖的函数。若catch的异常说明符是对象,则将派生类对象分割为它的基类对象。
异常捕获的匹配规则
异常的重新抛出原因:有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理。
void func1(int i)
{
if(i<0)
throw -1;
if(i>100)
throw -2;
}
void func2()
{
try
{ int m=0;
func1(199);
}
catch (int i)
{
cout<<"Error Code:"<<i<<endl;
}
}
void func3()
{
try
{
func2();
}
catch (int m)
{
cout<<"Error Code:"<<m<<endl;
}
}
int main()
{
func2();
func3();
system("pause");
return 0;
}