增强错误恢复能力是提高代码健壮性的最有力途径之一
之所以平时编写代码的时候不愿意去写错误处理,主要是由于这项工作及其无聊并可能导致代码膨胀,导致的结果就是本来就比较复杂的程序变得更加复杂。当然了,前面的缘由主要是针对C语言的,原因就在于C语言的‘紧耦合’性,必须在接近函数调用的地方使用错误处理,当然会增加复杂性了。
1.传统的错误处理(主要是针对C语言的方法)
1)函数中返回错误信息,或者设置一个全局的错误状态。导致的问题就和前面说到的一样,代码数量的爆炸,而且,从一个错误的函数中返回的东西本身也没什么意义。
2)使用鲜为人知的信号处理。由函数signal()和函数raise()。当然了,这样的话耦合度还是相当的高。
3)使用标准库中非局部跳转函数:setjump()和longjump(), 使用setjump()可以保存程序中已知的一个无错误状态,一旦发生错误,可以使用longjump()返回到该状态
下面的代码演示了setjump()和longjump()的使用方法(用C++描述)
[cpp]
/*
对函数setjmp(),如果直接调用,便会将当前处理器相关的信息保存到jmp_buf中并返回0
但如果使用同一个jmp_buf调用longjmp(),则函数就会返回到setjmp刚刚返回的地方
这次的返回值是longjmp的第二个参数
与goto语句的差别是,使用longjmp()可以返回任何预先确定的位置
*/
#include
#include
using namespace std;
class Rainbow
{
public:
Rainbow(){cout<<"Rainbow()"<
jmp_buf kansas;
void oz()
{
Rainbow rb;
for(int i=0;i<3;i++)
cout<<"there's no place like home"<
}
int main()
{
if(setjmp(kansas)==0)
{
cout<<"toenado,witch,munchkins..."<
}
else
{
cout<<"Auntie Em!"<<"I had the strangest dream..."<
return 0;
}
/*
对函数setjmp(),如果直接调用,便会将当前处理器相关的信息保存到jmp_buf中并返回0
但如果使用同一个jmp_buf调用longjmp(),则函数就会返回到setjmp刚刚返回的地方
这次的返回值是longjmp的第二个参数
与goto语句的差别是,使用longjmp()可以返回任何预先确定的位置
*/
#include
#include
using namespace std;
class Rainbow
{
public:
Rainbow(){cout<<"Rainbow()"<
jmp_buf kansas;
void oz()
{
Rainbow rb;
for(int i=0;i<3;i++)
cout<<"there's no place like home"<
}
int main()
{
if(setjmp(kansas)==0)
{
cout<<"toenado,witch,munchkins..."<
}
else
{
cout<<"Auntie Em!"<<"I had the strangest dream..."<
return 0;
}程序的运行结果如下:
可以看到,程序并没有调用类的析构函数,而这样本身就是异常现象(C++定义的),所以,这些函数不适合C++。
2.抛出异常
当代码出现异常的时候,可以创建一个包含错误信息的对象并抛出当前语境,如下:
[cpp]
#include
using namespace std;
class MyError
{
const char* const data;
public:
MyError(const char* const msg=0):data(msg){}
};
void f()
{
throw MyError("Something bad happen");
}
/*
当然了。这里没有使用try,程序会报错
*/
int main()
{
f();
return 0;
}
#include
using namespace std;
class MyError
{
const char* const data;
public:
MyError(const char* const msg=0):data(msg){}
};
void f()
{
throw MyError("Something bad happen");
}
/*
当然了。这里没有使用try,程序会报错
*/
int main()
{
f();
return 0;
}throw首先会创建程序所抛出对象的一个拷贝,包含throw表达式的函数返回了这个对象,异常发生之前所创建的局部对象被销毁,这种被称为“栈反解”。而程序员需要为每一种不同的异常抛出不同的对象。
3.捕获异常
就像前面所说的,如果一个函数通过throw出了一个对象,那么函数就会返回这个错误对象并退出。如果不想退出这个函数,,那么就可以设置一个try块。这个块被称作try的原因是程序需要在这里尝试调用各种函数。
当然,被抛出的异常会在某个地方被终止,这个地方就是异常处理器(catch)。
异常处理器紧跟在try之后,一旦某个异常被抛出,异常处理机制就会依次寻找参数类型与异常类型相匹配的异常处理器。找到后就会进入catch语句,于是系统就认为这个异常已经处理了。
下面通过对前面的setjump()和longjump()进行修改得到的程序:
[cpp]
#include
using namespace std;
class Rainbow
{
public:
Rainbow(){cout<<"Rainbow()"<
void oz()
{
Rainbow rb;
for(int i=0;i<3;i++)
cout<<"there's no place like home"<
}
int main()
{
try{
cout<<"toenado,witch,munchkins..."<
}catch(int){
cout<<"Auntie Em!"<<"I had the strangest dream..."<
return 0;
}
#include
using namespace std;
class Rainbow
{
public:
Rainbow(){cout<<"Rainbow()"<
void oz()
{
Rainbow rb;
for(int i=0;i<3;i++)
cout<<"there's no place like home"<
}
int main()
{
try{
cout<<"toenado,witch,munchkins..."<
}catch(int){
cout<<"Auntie Em!"<<"I had the strangest dream..."<
return 0;
}
程序的运行结果:
当执行throw语句时,程序的控制流程开始回溯,直到找到带有int参数的catch为止。程序在这里继续恢复执行。当然了,当程序从oz()中返回时,是会调用析构函数的。
在异常处理中有两个基本的模型:终止于恢复
终止:无论抛出了什么异常,程序都无法挽救,不需要返回发生异常的地方。
恢复:自动重新执行发生错误的代码。在C++中,必须显示的将程序的执行流程转移到错误发生的地方,通常是重新调用发生错误的函数,例