c++自定义异常处理汇总
(2013-07-31 16:02:18)分类: c++ |
例1:自定义一个继承自excepton的异常类myException
C++标准中,定义在中的任何异常类都派生自exception
Class,本例也只是简单地由exception继承,在try段抛出一个异常并捕捉。代码如下:
[cpp] view plaincopyprint?
#include
#include
using namespace std;
//customized exception class 'myException'
class myException:public exception
{
public:
};
//entry of the application
int main()
{
}
#include
#include
using namespace std;
//customized exception class 'myException'
class myException:public exception
{
public:
myException():exception("ERROR! Don't divide a number by
integer zero.\n")
{
}
};
//entry of the application
int main()
{
int x=100,y=0;
try
{
if(y==0) throw myException();
else cout<<x/y;
}
catch(myException& me)
{
cout<<me.what();
}
system("pause");
return 0;
}结果如下:
ERROR! Don't divide a number by integer zero.
请按任意键继续. . .
显然,异常被捕捉到了。此处需要说明的是,VC对异常处理类exception进行了扩展,本例之所以能够使用exception("ERROR!....")的初始化方法正出于这样的原因,C++标准是不允许这样做的。
与此同时,VC又没有遵循标准,有力地支持terminate和unexpected,它只保留了语法,却在编译运行时不提供支持。为了结合terminate和unexpected更加深入了解C++的异常处理,下面的例子采用Dev
cpp IDE实现。
例2:依照C++标准实现自定义异常类myException并将throw语句封装到函数check()中
涉及到的更改正如标题所述,(1)重写基类的what()函数,返回错误信息;(2)将throw
myException()封装到check()函数中;(3)允许check()函数抛出myException类型的异常。代码如下:
[cpp] view plaincopyprint?
#include
#include
using namespace std;
//customized exception class 'myException'
class myException:public exception
{
public:
};
void check(int y) throw(myException)//#2
{
}
//entry of the application
int main()
{
}
#include
#include
using namespace std;
//customized exception class 'myException'
class myException:public exception
{
public:
};
void check(int y) throw(myException)//#2
{
}
//entry of the application
int main()
{
int x=100,y=0;
try
{
check(y);
cout<<x/y;
}
catch(myException& me)
{
cout<<me.what();
}
system("pause");
return 0;
}结果与例1完全相同。需说明的是,紧跟check()后的throw列表表明允许该函数抛出的异常类型。这里不得不产生疑问,如果抛出了一个不被允许的异常类型将怎样?
例3:抛出unexpected异常
check函数体之后的throw列表,规定了允许抛出的异常类型,一旦违背,就将触发unexpected。可以把unexpected看作系统自动调用的CALLBACK函数,不同的是,也可以手工触发它的执行。本例的情况属于前者。代码如下:
[cpp] view plaincopyprint?
#include
#include
using namespace std;
//customized exception class 'myException'
class myException:public exception
{
public:
};
void check(int y) throw()//#1 only int-type exception is
permitted
{
}
void myUnexpected()
{
}
//entry of the application
int main()
{
}
#include
#include
using namespace std;
//customized exception class 'myException'
class myException:public exception
{
public:
};
void check(int y) throw()//#1 only int-type exception is
permitted
{
}
void myUnexpected()
{
}
//entry of the application
int main()
{
int x=100,y=0;
try
{
check(y);
cout<<x/y;
}
catch(myException& me)
{
cout<<me.what();
}
system("pause");
return 0;
}
结果如下:
Unexpected exception caught!
请按任意键继续. . .
check函数的throw列表为空,即不允许抛出任何类型的异常,然而实际上当异常发生时,系统不能等闲视之,它将调用unexpected处理方法。所以,限定一个函数throw列表为空是值得程序员警醒的事,需要特别留意。如果将#1处的代码修改为throw(int)等也能得到相同的结果。所谓unexpected异常,说白了就是函数体允许抛出异常类型范围之外的异常。如果check函数后面根本没有throw,则表示函数任何类型的异常都被允许。
例4:抛出函数体允许的异常,但没被捕捉到的情况
思考这样一个问题,如果函数check的throw列表中有异常类型myException,而且在y==0时,它的确抛出myException类型的异常,但是没有被catch到,这时会发生什么?
在正式回答这个问题之前,先讨论“没被catch到”的意思。比如,修改例3的代码如下:(##为修改之处)
[cpp] view plaincopyprint?
#include
#include
using namespace std;
//customized exception class 'myException'
class myException:public exception
{
public:
};
void check(int y) //any type of exception is permitted
{
}
void myUnexpected()
{
}
//entry of the application
int main()
{
}
#include
#include
using namespace std;
//customized exception class 'myException'
class myException:public exception
{
public:
};
void check(int y) //any type of exception is permitted
{
}
void myUnexpected()
{
}
//entry of the application
int main()
{
int x=100,y=0;
try
{
check(y);
cout<<x/y;
}
catch(int &e) //##1 no catch sentence matches
the throw type
{
cout<<e<<endl;
}
system("pause");
return 0;
}
编译运行,程序将会出错,因为check函数抛出的myException异常没有被处理。在缺省情况下,一旦出现抛出异常没被处理的问题,系统将自动调用abort()函数,终止程序允许,在控制台将会看到这样的提示:
This application has requested the Runtime to terminate it in
an unusual way.Please contact the application's support team for
more information.
不过可以增加##2部分的代码,catch(...)表示捕捉任何类型的异常。
注意:check函数不被允许的异常类型并不会进入到catch语句的判断中来,因此catch(...)对unexpected
exception没有作用。
仍然考虑没有##2部分的情况。正如前面所述,系统将自动调用abort()函数终止程序。实际上,它触发的是terminate,类似于unexpected,仍然可以自定义terminate的处理方法。甚至terminate语法上跟unexpected都十分近似。修改代码为:
[cpp] view plaincopyprint?
#include
#include
using namespace std;
//customized exception class 'myException'
class myException:public exception
{
public:
};
void check(int y) //any type of exception is permitted
{
}
void myUnexpected()
{
}
void myTerminate() //##1 set it be the terminate handler
{
}
//entry of the application
int main()
{
}
#include
#include
using namespace std;
//customized exception class 'myException'
class myException:public exception
{
public:
};
void check(int y) //any type of exception is permitted
{
}
void myUnexpected()
{
}
void myTerminate() //##1 set it be the terminate handler
{
}
//entry of the application
int main()
{
int x=100,y=0;
try
{
check(y);
cout<<x/y;
}
catch(int &e) //no catch sentence matches the
throw type
{
cout<<e<<endl;
}
system("pause");
return 0;
}
结果如下:
Unhandler exception!
请按任意键继续. . .
结论:C++为异常处理提供了友好的支持。
用户可以自定义异常类型,异常类型并不受到限制,可以是内建数据类型如int,double等,也可以是自定义的类,也可以从C++某个异常类继承下来。例1采用了派生自exception的方法。
除此之外,在定义函数时,可以显式指定函数体抛出的异常类型。隐式情况下,缺省允许函数抛出任何类型的异常。有可以增加throw语句,对异常类型加以限制。特别的是,throw()表示不允许函数抛出任何类型的异常。如果违反了throw列表规定的异常类型,系统将调用unexpected
hanlder进行处理,可以自定义unexpected异常处理方法。例2和例3对它们进行了说明。
如果对于函数体throw列表合法的异常被抛出,但是却没有被程序捕捉处理,系统将调用terminate
handler进行处理。缺省情况下,只是简单调用abort()函数终止程序,同样可以自定义terminate处理方法。例4对它进行了说明。
转自:http://blog.csdn.net/btwsmile/article/details/6685549
////////////////////另一篇相关网摘////////////////
引言
异常,让一个函数可以在发现自己无法处理的错误时抛出一个异常,希望它的调用者可以直接或者间接处理这个问题。而传统错误处理技术,检查到一个局部无法处理的问题时:
1.终止程序(例如atol,atoi,输入NULL,会产生段错误,导致程序异常退出,如果没有core文件,找问题的人一定会发疯)
2.返回一个表示错误的值(很多系统函数都是这样,例如malloc,内存不足,分配失败,返回NULL指针)
3.返回一个合法值,让程序处于某种非法的状态(最坑爹的东西,有些第三方库真会这样)
4.调用一个预先准备好在出现"错误"的情况下用的函数。
第一种情况是不允许的,无条件终止程序的库无法运用到不能当机的程序里。第二种情况,比较常用,但是有时不合适,例如返回错误码是int,每个调用都要检查错误值,极不方便,也容易让程序规模加倍(但是要精确控制逻辑,我觉得这种方式不错)。第三种情况,很容易误导调用者,万一调用者没有去检查全局变量errno或者通过其他方式检查错误,那是一个灾难,而且这种方式在并发的情况下不能很好工作。至于第四种情况,本人觉得比较少用,而且回调的代码不该多出现。
使用异常,就把错误和处理分开来,由库函数抛出异常,由调用者捕获这个异常,调用者就可以知道程序函数库调用出现错误了,并去处理,而是否终止程序就把握在调用者手里了。
但是,错误的处理依然是一件很困难的事情,C++的异常机制为程序员提供了一种处理错误的方式,使程序员可以更自然的方式处理错误。
异常实战入门
假设我们写一个程序,把用户输入的两个字符串转换为整数,相加输出,一般我们会这么写
char *str1 = "1", *str2 = "2";int num1 = atoi(str1);int num2 =
atoi(str2);printf("sum is %d\n", num1 +
num2);
假设用户输入的是str1,str2,如果str1和str2都是整数类型的字符串,这段代码是可以正常工作的,但是用户的输入有可能误操作,输入了非法字符,例如
char *str1 = "1", *str2 = "a";int num1 = atoi(str1);int num2 =
atoi(str2);printf("sum is %d\n", num1 +
num2);这个时候结果是1,因为atoi(str2)返回0。
如果用户输入是这样:
char *str1 = "1", *str2 = NULL;int num1 = atoi(str1);int num2
= atoi(str2);printf("sum is %d\n", num1 +
num2);那么这段代码会出现段错误,程序异常退出。
atoi我觉得是一个比较危险的函数,如果在一个重要系统中,调用者不知情,传入了一个NULL字符,程序就异常退出了,导致服务中断,或者传入非法字符,结果返回0,代码继续走下去,在复杂的系统中想要定位这个问题,真是很不容易。
所以比较合适的方式,是我们用异常处理改造一个安全的atoi方法,叫parseNumber。
class NumberParseException {};bool isNumber(char * str) {
using namespace std;
if (str == NULL)
return false;
int len = strlen(str);
if (len == 0)
return
false; bool isaNumber =
false; char ch;
for (int i = 0; i
< len; i++) {
if (i == 0
&& (str[i] == '-' || str[i] ==
'+'))
continue;
if (isdigit(str[i])) {
isaNumber = true;
} else {
isaNumber
= false;
break;
}
}
return isaNumber;}int parseNumber(char * str)
throw(NumberParseException) {
if (!isNumber(str))
throw
NumberParseException(); return
atoi(str);}上述代码中NumberParseException是自定义的异常类,当我们检测的时候传入的str不是一个数字时,就抛出一个数字转换异常,让调用者处理错误,这比传入NULL字符串,导致段错误结束程序好得多,调用者可以捕获这个异常,决定是否结束程序,也比传入一个非整数字符串,返回0要好,程序出现错误,却继续无声无息执行下去。
于是我们之前写的代码可以改造如下:
输入非法,抛出NumberParseException,即使调用者没有考虑输入是非法的,例如是:
这里介绍了异常的抛出和捕获,还有异常的使用场景,接下来就开始一步步讲解C++异常。
异常的描述
函数和函数可能抛出的异常集合作为函数声明的一部分是有价值的,例如
void f(int a) throw
(x2,x3);表示f()只能抛出两个异常x2,x3,以及这些类型派生的异常,但不会抛出其他异常。如果f函数违反了这个规定,抛出了x2,x3之外的异常,例如x4,那么当函数f抛出x4异常时,
会转换为一个std::unexpected()调用,默认是调用std::terminate(),通常是调用abort()。
如果函数不带异常描述,那么假定他可能抛出任何异常。例如:
int f(); //可能抛出任何异常不带任何异常的函数可以用空表表示:
int g() throw (); // 不会抛出任何异常捕获异常
捕获异常的代码一般如下:
try { throw E();}catch (H
h) {
//何时我们可以能到这里呢}1.如果H和E是相同的类型
2.如果H是E的基类
3.如果H和E都是指针类型,而且1或者2对它们所引用的类型成立
4.如果H和E都是引用类型,而且1或者2对H所引用的类型成立
从原则上来说,异常在抛出时被复制,我们最后捕获的异常只是原始异常的一个副本,所以我们不应该抛出一个不允许抛出一个不允许复制的异常。
此外,我们可以在用于捕获异常的类型加上const,就像我们可以给函数加上const一样,限制我们,不能去修改捕捉到的那个异常。
还有,捕获异常时如果H和E不是引用类型或者指针类型,而且H是E的基类,那么h对象其实就是H h =
E(),最后捕获的异常对象h会丢失E的附加携带信息。
异常处理的顺序
我们之前写的parseNumber函数会抛出NumberParseException,这个函数只是判断是否数字才抛出异常,但是没有考虑,但这个字符串表示的整数太大,溢出,抛出异常Overflow.表示如下:
class NumberParseException {};class Overflow : public
NumberParseException
{};假设我们parseNumber函数已经为字符串的整数溢出做了检测,遇到这种情况,会抛出Overflow异常,那么异常捕获代码如下:
如果没有异常的层次结构,当函数升级加入新的异常描述时,我们可能都要修改代码,为每一处调用这个函数的地方加入对应的catch新的异常语句,这很让你厌烦,程序员也很容易忘记把某个异常加入列表,导致这个异常没有捕获,异常退出。
而有了异常的层次结构,函数升级之后,例如我们的parseNumber加入了Overflow异常描述,函数调用者只需要在自己感兴趣的调用场景加入catch(Overflow),并做处理就行了,如果根据不关心Overflow错误,甚至不用修改代码。
未捕获的异常
如果抛出的异常未被捕捉,那么就会调用函数std::terminate(),默认情况是调用abort,这对于大部分用户是正确选择,特别是排错程序错误的阶段(调用abort会产生coredump文件,coredump文件的使用可以参考博客的"学会用core
dump调试程序错误")。
如果我们希望在发生未捕获异常时,保证清理工作,可以在所有真正需要关注的异常处理之外,再在main添加一个捕捉一切的异常处理,例如:
int main() { try {
//... }
catch (std::range_error) {
cerr << "range
error\n"; } catch
(std::bad_alloc) {
cerr
<< "new run out of memory\n";
} catch (...) {
//..
}}这样就可以捕捉所有的异常,除了那些在全局变量构造和析构的异常(如果要获得控制,唯一方式是set_unexpected)。
其中catch(...)表示捕捉所有异常,一般会在处理代码做一些清理工作。
重新抛出
当我们捕获了一个异常,却发现无法处理,这种情况下,我们会做完局部能够做的事情,然后再一次抛出这个异常,让这个异常在最合适的地方地方处理。例如:
void downloadFileFromServer() {
try {
connect_to_server();
//... }
catch
(NetworkException) {
if
(can_handle_it_completely) {
//处理网络异常,例如重连
} else {
throw;
}
}}这个函数是从远程服务器下载文件,内部调用连接到远程服务器的函数,但是可能存在着网络异常,如果多次重连无法成功,就把这个网络异常抛出,让上层处理。
重新抛出是采用不带运算对象的throw表示,但是如果重新抛出,又没有异常可以重新抛出,就会调用terminate();
假设NetworkException有两个派生异常叫FtpConnectException和HttpConnectException,调用connect_to_server时是抛出HttpConnectException,那么调用downloadFileFromServer仍然能捕捉到异常HttpConnectException。
标准异常
到了这里,你已经基本会使用异常了,可是如果你是函数开发者,并需要把函数给别人使用,在使用异常时,会涉及到自定义异常类,但是C++标准已经定义了一部分标准异常,请尽可能复用这些异常,标准异常参考http://www.cplusplus.com/reference/std/stdexcept/
虽然C++标准异常比较少,但是作为函数开发者,尽可能还是复用c++标准异常,作为函数调用者就可以少花时间去了解的你自定义的异常类,更好的去调用你开发的函数。
总结
本文只是简单从异常的使用场景,再介绍异常的基本使用方法,一些高级的异常用法没有罗列,详细资料可以参考c++之父的C++程序设计语言的异常处理。
前一篇:[转载]类对象和类指针的区别
后一篇:vs2012快捷键