异常,让一个函数可以在发现自己无法处理的错误时抛出一个异常,希望它的调用者可以直接或者间接处理这个问题。而传统错误处理技术,检查到一个局部无法处理的问题时:
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要好,程序出现错误,却继续无声无息执行下去。
于是我们之前写的代码可以改造如下:
char *str1 = "1", *str2 = NULL; try { int num1 = parseNumber(str1); int num2 = parseNumber(str2); printf("sum is %d\n", num1 + num2); } catch (NumberParseException) { printf("输入不是整数\n"); }
这段代码的结果是打印出"输入不是整数".假设这段代码是运行在一个游戏统计系统中,系统需要定时从大量文件中统计大量用户进入游戏频道1和游戏频道2的次数,str1代表进入游戏频道1的次数,str2表示进入频道2的次数,如果不是使用异常,当输入是NULL程序会导致整个系统宕机,当输入是非法整数,计算结果全部是错误的,当时程序仍然无声无息"正确执行"。
输入非法,抛出NumberParseException,即使调用者没有考虑输入是非法的,例如是:
char *str1 = "1", *str2 = "12,"; int num1 = parseNumber(str1); int num2 = parseNumber(str2); printf("sum is %d\n", num1 + num2);
就算调用者比较粗心,没有捕获异常,程序运行中会抛出NumberParseException,程序宕机,会留下coredump文件,调用者通过"gdb 程序名 coredump文件",查看程序宕机时的堆栈,就知道程序运行中,出现了非法整数字符,那么他就很快知道问题所在,会学乖,把上述代码改成
char *str1 = "1", *str2 = NULL; try { int num1 = parseNumber(str1); int num2 = parseNumber(str2); printf("sum is %d\n", num1 + num2); } catch (NumberParseException) { printf("输入不是整数\n"); //打印文件的路径,行号,str1,str2等信息足够自己去定位问题所在 }
这样,下次程序出现问题时,调用者就可以定位问题所在了,这就是异常的错误处理方式,把错误的发现(parseNumber)和错误的处理(游戏统计代码)分开。
这里介绍了异常的抛出和捕获,还有异常的使用场景,接下来就开始一步步讲解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异常,那么异常捕获代码如下:
char *str1 = "1", *str2 = NULL; try { int num1 = parseNumber(str1); int num2 = parseNumber(str2); printf("sum is %d\n", num1 + num2); } catch (Overflow) { //处理Overflow或者任何由Overflow派生的异常 } catch (NumberParseException) { //处理不是Overflow的NumberParseException异常 }
异常组织这种层次结构对于代码的健壮性很重要,因为库函数发布之后,不可能不加入新的异常,就像我们的parseNumber,第一次发布时只是考虑输入是否一个整数的错误,第二次发布时就考虑了判断输入的一个字符串作为整数是否太大溢出,对于一个函数发布之后不再添加新的异常,几乎所有的库函数都不能接受。
如果没有异常的层次结构,当函数升级加入新的异常描述时,我们可能都要修改代码,为每一处调用这个函数的地方加入对应的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++程序设计语言的异常处理。
转载:http://blog.csdn.net/zzjxiaozi/article/details/6649999
try代码段是你要检测的代码
runtime_error 仅运行时才能检测到的问题
range_error 结果超出有意义的值域范围
overflow_error 计算上溢
underflow_error 下溢
logic_error 逻辑错误:可在运行前检测到的问题
domain_error 参数结果值不存在
invalid_argument 不合适的参数
length_error 超过该类型最大长度
out_of_range 使用一个超出有效范围的值
抛出异常(也称为抛弃异常)即检测是否产生异常,在C++中,其采用throw语句来实现,如果检测到产生异常,则抛出异常。该语句的格式为:
throw 表达式;
如果在try语句块的程序段中(包括在其中调用的函数)发现了异常,且抛弃了该异常,则这个异常就可以被try语句块后的某个catch语句所捕获并处理,捕获和处理的条件是被抛弃的异常的类型与catch语句的异常类型相匹配。由于C++使用数据类型来区分不同的异常,因此在判断异常时,throw语句中的表达式的值就没有实际意义,而表达式的类型就特别重要。
【范例20-2】处理除数为0的异常。该范例将上述除数为0的异常可以用try/catch语句来捕获异常,并使用throw语句来抛出异常,从而实现异常处理,实现代码如代码清单20-2所示。
代码清单20-2
1 #include<iostream.h> //包含头文件
2 #include<stdlib.h>
3 double fuc(double x, double y) //定义函数
4 {
5 if(y==0)
6 {
7 throw y; //除数为0,抛出异常
8 }
9 return x/y; //否则返回两个数的商
10 }
11 void main()
12 {
13 double res;
14 try //定义异常
15 {
16 res=fuc(2,3);
17 cout<<"The result of x/y is : "<<res<<endl;
18 res=fuc(4,0); //出现异常
19 }
20 catch(double) //捕获并处理异常
21 {
22 cerr<<"error of dividing zero.\n";
23 exit(1); //异常退出程序
24 }
25 }
【运行结果】在Visual C++中新建一个【C++ Source File】文件,输入上述的代码,编译无误后运行。
【范例解析】上述代码中,在主函数main()的第14~19行中使用了try语句定义异常,其中包含3条有可能出现异常的语句,它们为调用两个数相除的函数。在代码的第20~24行定义了异常处理,即捕获异常后执行该段代码中的语句。此外,在函数fuc()的代码5~8行通过throw语句抛出异常。
注意:一般来说,throw语句通常与try- catch或try-finally语句一起使用,可以使用throw语句显式引发异常。
////////////
c++ try_catch
1、基础介绍
try
{
//程序中抛出异常
throw value;
}
catch(valuetype v)
{
//例外处理程序段
}
语法小结:throw抛出值,catch接受,当然,throw必须在“try语句块”中才有效。
2、深入throw:
(i)、程序接受到throw语句后就会自动调用析构器,把该域(try后的括号内)对象clean up,然后再进
入catch语句(如果在循环体中就退出循环)。
这种机制会引起一些致命的错误,比如,当“类”有指针成员变量时(又是指针!),在 “类的构建器
”中的throw语句引起的退出,会导致这个指针所指向的对象没有被析构。这里很基础,就不深入了,提
示一下,把指针改为类就行了,比如模板类来代替指针,在模板类的内部设置一个析构函数。
(ii)、语句“throw;”抛出一个无法被捕获的异常,即使是catch(...)也不能捕捉到,这时进入终止函数
,见下catch。
3、深入catch:
一般的catch出现的形式是:
try{}
catch(except1&){}
catch(except2&){}
catch(...){} //接受所有异常
一般都写成引用(except1&),原因很简单,效率。
问题a:抛出异常,但是catch不到异常怎么办?(注意没有Java类似的finally语句)
在catch没有捕获到匹配的异常的时候,会调用默认的终止函数。可以调用set_terminate()来设置终止函数,参数是一个函数指针,类型是:void (*terminate)()。
到这里,可以题个问题:“没有try-catch,直接在程序中"throw;",会怎么样?”
其他一些技巧:
4、try一个函数体,形式如下
void fun(type1,type2) try----try放在函数体后
{
函数定义
}
catch(typeX){}
这个用法的效果就相当于:
void fun()
{
try{函数定义}
}
5、throw一个函数体,形式如下:
void fun (); // 能抛出任何类型的异常
void fun () throw(except1,except2,except3)
// 后面括号里面是一个异常参数表,本例中只能抛出这3中异常
void fun () throw() // 参数表为空,不能抛出异常
问题b:假设fun()中抛出了一个不在“异常参数表”中的异常,会怎么样?
答:调用set_terminate()中设定的终止函数。然而,这只是表面现象,实际上是调用默认的unexpected()函数,然而这个默认的unexpected()调用了set_terminate()中设定的终止函数。可以用set_unexpected()来设置 unexpected,就像set_terminate()一样的用法,但是在设定了新的“unexpected()”之后,就不会再调用 set_terminater中设定的终止函数了。
这个语法是很有用的,因为在用别人的代码时,不知道哪个地方会调用什么函数又会抛出什么异常,用一个异常参数表在申明时限制一下,很实用。
以前都是用try{} catch(…){}来捕获C++中一些意想不到的异常, 今天看了Winhack的帖子才知道,这种方法在VC中其实是靠不住的。例如下面的代码:
这段代码在debug下没有问题,异常会被捕获,会弹出”catched”的消息框。 但在Release方式下如果选择了编译器代码优化选项,则VC编译器会去搜索try块中的代码, 如果没有找到throw代码, 他就会认为try catch结构是多余的, 给优化掉。 这样造成在Release模式下,上述代码中的异常不能被捕获,从而迫使程序弹出错误提示框退出。
那么能否在release代码优化状态下捕获这个异常呢, 答案是有的。 就是__try, __except结构, 上述代码如果改成如下代码异常即可捕获。
但是用__try, __except块还有问题, 就是这个不是C++标准, 而是Windows平台特有的扩展。 而且如果在使用过程中涉及局部对象析构函数的调用,则会出现C2712 的编译错误。 那么还有没有别的办法呢?
当然有, 就是仍然使用C++标准的try{}catch(..){}, 但在编译命令行中加入 /EHa 的参数。这样VC编译器不会把try catch模块给优化掉了。
一篇比较好的英文文章谈这个问题: http://members.cox.net/doug_web/eh.htm
C++中catch(…)如何使用:
上一篇文章中详细讲了讲C++异常处理模型的trycatch使用语法,其中catch关键字是用来定义catch block的,它后面带一个参数,用来与异常对象的数据类型进行匹配。注意catch关键字只能定义一个参数,因此每个catch block只能是一种数据类型的异常对象的错误处理模块。如果要想使一个catch block能抓获多种数据类型的异常对象的话,怎么办?C++标准中定义了一种特殊的catch用法,那就是” catch(…)”。
感性认识
1、catch(…)到底是一个什么样的东东,先来个感性认识吧!看例子先:
int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是int,值为1)
throw 1;
}
//catch( int& value )
//注意这里catch语句
catch( …)
{
cout << "在 catch(…) block 中, 抛出的int类型的异常对象被处理" << endl;
}
}
2、哈哈!int类型的异常被catch(…)抓获了,再来另一个例子:
int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是double,值为0.5)
throw 0.5;
}
//catch( double& value )
//注意这里catch语句
catch( …)
{
cout << "在 catch(…) block 中, double类型的异常对象也被处理" << endl;
}
}
3、同样,double类型的异常对象也被catch(…)块抓获了。是的,catch(..)能匹配成功所有的数据类型的异常对象,包括C++语言提 供所有的原生数据类型的异常对象,如int、double,还有char*、int*这样的指针类型,另外还有数组类型的异常对象。同时也包括所有自定义 的抽象数据类型。例程如下:
int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是char*)
char* p=0;
throw p;
}
//catch( char* value )
//注意这里catch语句
catch( …)
{
cout << "在 catch(…) block 中, char*类型的异常对象也被处理" << endl;
}
}
int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是int[])
int a[4];
throw a;
}
//catch( int value[] )
//注意这里catch语句
catch( …)
{
cout << "在 catch(…) block 中, int[]类型的异常对象也被处理" << endl;
}
}
4、对于抽象数据类型的异常对象。catch(…)同样有效,例程如下:
class MyException
{
public:
protected:
int code;
};
int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是MyException)
throw MyException();
}
//catch(MyException& value )
//注意这里catch语句
catch( …)
{
cout << "在catch(…) block中, MyException类型的异常对象被处理" << endl;
}
}
对catch(…)有点迷糊?
1、究竟对catch(…)有什么迷糊呢?还是看例子先吧!
void main()
{
int* p = 0;
try
{
// 注意:下面这条语句虽然不是throw语句,但它在执行时会导致系统
// 出现一个存储保护错误的异常(access violation exception)
*p = 13; // causes an access violation exception;
}
catch(...)
{
//catch(…)能抓获住上面的access violation exception异常吗?
cout << "在catch(…) block中" << endl;
}
}
请问上面的程序运行时会出现什么结果吗?catch(…)能抓获住系统中出现的access violation exception异常吗?朋友们!和我们的主人公阿愚一样,自己动手去测试一把!
结果又如何呢?实际上它有两种不同的运行结果,在window2000系统下用VC来测试运行这个小程序时,发现程序能输出"在catch(…) block中"的语句在屏幕上,也即catch(…) 能成功抓获住系统中出现的access violation exception异常,很厉害吧!但如果这个同样的程序在linux下用gcc编译后运行时,程序将会出现崩溃,并在屏幕上输出”segment fault”的错误信息。
主人公阿愚有点急了,也开始有点迷糊了,为什么?为什么?为什么同样一个程序在两种不同的系统上有不同的表现呢?其原因就是:对于这种由于硬件或操作 系统出现的系统异常(例如说被零除、内存存储控制异常、页错误等等)时,window2000系统有一个叫做结构化异常处理(Structured Exception Handling,SEH)的机制,这个东东太厉害了,它能和VC中的C++异常处理模型很好的结合上(实际上VC实现的C++异常处理模型很大程度上建 立在SEH机制之上的,或者说它是SEH的扩展,后面文章中会详细阐述并分析这个久富盛名的SEH,看看catch(…)是如何神奇接管住这种系统异常出 现后的程序控制流的,不过这都是后话)。而在linux系统下,系统异常是由信号处理编程方法来控制的(信号处理编程,signal processing progamming。在介绍unix和linux下如何编程的书籍中,都会有对信号处理编程详细的介绍,当然执著的主人公阿愚肯定对它也不会放过,会深 入到unix沿袭下来的信号处理编程内部的实现机制,并尝试完善改进它,使它也能够较好地和C++异常处理模型结合上)。
那么C++标准中对于这种同一个程序有不同的运行结果有何解释呢?这里需要注意的是,window2000系统下catch(…)能捕获住系统异常, 这完全是它自己的扩展。在C++标准中并没有要求到这一点,它只规定catch(…)必须能捕获程序中所有通过throw语句抛出的异常。因此上面的这个 程序在linux系统下的运行结果也完全是符合C++标准的。虽然大家也必须承认window2000系统下对C++异常处理模型的这种扩展确实是一个很 不错的完善,极大得提高了程序的安全性。
为什么要用catch(…)这个东东?
程序员朋友们也许会说,这还有问吗?这篇文章的一开始不就讲到了吗?catch(…)能够捕获多种数据类型的异常对象,所以它提供给程序员一种对异常 对象更好的控制手段,使开发的软件系统有很好的可靠性。因此一个比较有经验的程序员通常会这样组织编写它的代码模块,如下:
void Func()
{
try
{
// 这里的程序代码完成真正复杂的计算工作,这些代码在执行过程中
// 有可能抛出DataType1、DataType2和DataType3类型的异常对象。
}
catch(DataType1& d1)
{
}
catch(DataType2& d2)
{
}
catch(DataType3& d3)
{
}
// 注意上面try block中可能抛出的DataType1、DataType2和DataType3三
// 种类型的异常对象在前面都已经有对应的catch block来处理。但为什么
// 还要在最后再定义一个catch(…) block呢?这就是为了有更好的安全性和
// 可靠性,避免上面的try block抛出了其它未考虑到的异常对象时导致的程
// 序出现意外崩溃的严重后果,而且这在用VC开发的系统上更特别有效,因
// 为catch(…)能捕获系统出现的异常,而系统异常往往令程序员头痛了,现
// 在系统一般都比较复杂,而且由很多人共同开发,一不小心就会导致一个
// 指针变量指向了其它非法区域,结果意外灾难不幸发生了。catch(…)为这种
// 潜在的隐患提供了一种有效的补救措施。
catch(…)
{
}
}
还有,特别是VC程序员为了使开发的系统有更好的可靠性,往往在应用程序的入口函数中(如MFC框架的开发环境下 CXXXApp::InitInstance())和工作线程的入口函数中加上一个顶层的trycatch块,并且使用catch(…)来捕获一切所有的 异常,如下:
BOOL CXXXApp::InitInstance()
{
if (!AfxSocketInit())
{
AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
return FALSE;
}
AfxEnableControlContainer();
// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need.
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
// 注意这里有一个顶层的trycatch块,并且使用catch(…)来捕获一切所有的异常
try
{
CXXXDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
}
catch(…)
{
// dump出系统的一些重要信息,并通知管理员查找出现意外异常的原因。
// 同时想办法恢复系统,例如说重新启动应用程序等
}
// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
}
通过上面的例程和分析可以得出,由于catch(…)能够捕获所有数据类型的异常对象,所以在恰当的地方使用catch(…)确实可以使软件系统有着更 好的可靠性。这确实是大家使用catch(…)这个东东最好的理由。但不要误会的是,在C++异常处理模型中,不只有catch(…)方法能够捕获几乎所 有类型的异常对象(也许有其它更好的方法,在下一篇文章中主人公阿愚带大家一同去探讨一下),可C++标准中为什么会想到定义这样一个catch(…) 呢?有过java或C#编程开发经验的程序员会发现,在它们的异常处理模型中,并没有这样类似的一种语法,可这里不得不再次强调的是,java中的异常处 理模型是C++中的异常处理模型的完善改进版,可它反而没有了catch(…),为何呢?还是先去看看下一章吧,“C++的异常处理和面向对象的紧密关系 ”。也许大家能找到一个似乎合理的原因。
转载:http://zhidao.baidu.com/link?url=xYcKiyJr21X_zujJUQdsk6QPu_KFeVRIST-NNdXwoE_9jEJhSdMIu-vPMQWlhtxNtF7dr-KvVqhVp0cE1oytoa
一、简单的例子
catch(object^)表示捕获一个object类型的异常类。而这个类必须是在try语句块中被抛出的。例如:
#include<iostream>
#include<string>
using namespace std;
int main()
{
string ex = "this is a exception";
try
{
cout<<"before throw"<<endl;
throw ex;
cout<<"after throw"<<endl;
}
catch(string &e)
{
cout<<e<<endl;
}
cout<<"end"<<endl;
}
以上这个例子也说明了,你这个问题用try,catch是不能解决的。捕捉到异常后,是不可能再执行下一条语句,当然如果你放在catch语句块之后,是可以执行。
将抛出异常的语句放在try中,在catch中处理该异常,在catch之后继续执行就可以了
单刀直入,首先通过一个简单的例子来看基本的用法。
#include<iostream.h> //包含头文件
#include<stdlib.h>
double fuc(double x, double y) //定义函数
{
if(y==0)
{
throw y; //除数为0,抛出异常
}
return x/y; //否则返回两个数的商
}
void main()
{
double res;
try //定义异常
{
res=fuc(2,3);
cout<<"The result of x/y is : "<<res<<endl;
res=fuc(4,0); //出现异常
}
catch(double) //捕获并处理异常
{
cerr<<"error of dividing zero.\n";
exit(1); //异常退出程序
}
}
catch 的数据类型需要与throw出来的数据类型相匹配的。
二、catch(...)的作用
catch(…)能够捕获多种数据类型的异常对象,所以它提供给程序员一种对异常对象更好的控制手段,使开发的软件系统有很好的可靠性。因此一个比较有经验的程序员通常会这样组织编写它的代码模块,如下:
void Func()
{
try
{
// 这里的程序代码完成真正复杂的计算工作,这些代码在执行过程中
// 有可能抛出DataType1、DataType2和DataType3类型的异常对象。
}
catch(DataType1& d1)
{
}
catch(DataType2& d2)
{
}
catch(DataType3& d3)
{
}
// 注意上面try block中可能抛出的DataType1、DataType2和DataType3三
// 种类型的异常对象在前面都已经有对应的catch block来处理。但为什么
// 还要在最后再定义一个catch(…) block呢?这就是为了有更好的安全性和
// 可靠性,避免上面的try block抛出了其它未考虑到的异常对象时导致的程
// 序出现意外崩溃的严重后果,而且这在用VC开发的系统上更特别有效,因
// 为catch(…)能捕获系统出现的异常,而系统异常往往令程序员头痛了,现
// 在系统一般都比较复杂,而且由很多人共同开发,一不小心就会导致一个
// 指针变量指向了其它非法区域,结果意外灾难不幸发生了。catch(…)为这种
// 潜在的隐患提供了一种有效的补救措施。
catch(…)
{
}
}
三、异常中采用面向对象的处理
首先看下面的例子:
void OpenFile(string f)
{
try
{
// 打开文件的操作,可能抛出FileOpenException
}
catch(FileOpenException& fe)
{
// 处理这个异常,如果这个异常可以很好的得以恢复,那么处理完毕后函数
// 正常返回;否则必须重新抛出这个异常,以供上层的调用函数来能再次处
// 理这个异常对象
int result = ReOpenFile(f);
if (result == false) throw;
}
}
void ReadFile(File f)
{
try
{
// 从文件中读数据,可能抛出FileReadException
}
catch(FileReadException& fe)
{
// 处理这个异常,如果这个异常可以很好的得以恢复,那么处理完毕后函数
// 正常返回;否则必须重新抛出这个异常,以供上层的调用函数来能再次处
// 理这个异常对象
int result = ReReadFile(f);
if (result == false) throw;
}
}
void WriteFile(File f)
{
try
{
// 往文件中写数据,可能抛出FileWriteException
}
catch(FileWriteException& fe)
{
// 处理这个异常,如果这个异常可以很好的得以恢复,那么处理完毕后函数
// 正常返回;否则必须重新抛出这个异常,以供上层的调用函数来能再次处理这个异对象
int result = ReWriteFile(f);
if (result == false) throw;
}
}
void Func()
{
try
{
// 对文件进行操作,可能出现FileWriteException、FileWriteException
// 和FileWriteException异常
OpenFile(…);
ReadFile(…);
WriteFile(…);
}
// 注意:FileException是FileOpenException、FileReadException和FileWriteException
// 的基类,因此这里定义的catch(FileException& fe)能捕获所有与文件操作失败的异
// 常。
catch(FileException& fe)
{
ExceptionInfo* ef = fe.GetExceptionInfo();
cout << “操作文件时出现了不可恢复的错误,原因是:”<< fe << endl;
}
}
下面是更多面向对象和异常处理结合的例子:
#include <iostream.h>
class ExceptionClass
{
char* name;
public:
ExceptionClass(const char* name="default name")
{
cout<<"Construct "<<name<<endl;
this->name=name;
}
~ExceptionClass()
{
cout<<"Destruct "<<name<<endl;
}
void mythrow()
{
throw ExceptionClass("my throw");
}
}
void main()
{
ExceptionClass e("Test");
try
{
e.mythrow();
}
catch(...)
{
cout<<”*********”<<endl;
}
}
这是输出信息:
Construct Test
Construct my throw
Destruct my throw
****************
Destruct my throw (这里是异常处理空间中对异常类的拷贝的析构)
Destruct Test
======================================
不过一般来说可能更习惯于把会产生异常的语句和要throw的异常类分成不同的类来写,下面的代码可以是更愿意书写的:
class ExceptionClass
{
public:
ExceptionClass(const char* name="Exception Default Class")
{
cout<<"Exception Class Construct String"<<endl;
}
~ExceptionClass()
{
cout<<"Exception Class Destruct String"<<endl;
}
void ReportError()
{
cout<<"Exception Class:: This is Report Error Message"<<endl;
}
};
class ArguClass
{
char* name;
public:
ArguClass(char* name="default name")
{
cout<<"Construct String::"<<name<<endl;
this->name=name;
}
~ArguClass()
{
cout<<"Destruct String::"<<name<<endl;
}
void mythrow()
{
throw ExceptionClass("my throw");
}
};
_tmain()
{
ArguClass e("haha");
try
{
e.mythrow();
}
catch(int)
{
cout<<"If This is Message display screen, This is a Error!!"<<endl; //这行不会执行
}
catch(ExceptionClass pTest)
{
pTest.ReportError();
}
catch(...)
{
cout<<"***************"<<endl;
}
}
输出Message:
Construct String::haha
Exception Class Construct String
Exception Class Destruct String
Exception Class:: This is Report Error Message
Exception Class Destruct String
Destruct String::haha
ps:http://blog.csdn.net/goodlixueyong/article/details/6533733
C++有很多的标准异常类:
namespace std
{
//exception派生
class logic_error; //逻辑错误,在程序运行前可以检测出来
//logic_error派生
class domain_error; //违反了前置条件
class invalid_argument; //指出函数的一个无效参数
class length_error; //指出有一个超过类型size_t的最大可表现值长度的对象的企图
class out_of_range; //参数越界
class bad_cast; //在运行时类型识别中有一个无效的dynamic_cast表达式
class bad_typeid; //报告在表达试typeid(*p)中有一个空指针p
//exception派生
class runtime_error; //运行时错误,仅在程序运行中检测到
//runtime_error派生
class range_error; //违反后置条件
class overflow_error; //报告一个算术溢出
class bad_alloc; //存储分配错误
}
请注意观察上述类的层次结构,可以看出,标准异常都派生自一个公共的基类exception。基类包含必要的多态性函数提供异常描述,可以被重载。下面是exception类的原型:
class exception
{
public:
exception() throw();
exception(const exception& rhs) throw();
exception& operator=(const exception& rhs) throw();
virtual ~exception() throw();
virtual const char *what() const throw();
};
其中的一个重要函数为what(),它返回一个表示异常的字符串指针。
标准库异常类定义在以下四个头文件中
1、exception头文件:定义了最常见的标准异常类,其类名为exception。只通知异常的产生,但不会提供更多的信息
2、stdexcept头文件定义了以下几种常见异常类
函数 功能或作用
exception 最常见的问题
runtime_error 运行时错误:仅在运行时才能检测到的问题
range_error 运行时错误:生成的结果超出了有意义的值域范围
overflow_error 运行时错误:计算上溢
underflow_error 运行时错误:计算下溢
logic_error 逻辑错误:可在运行前检测到的问题
domain_error 逻辑错误:参数的结果值不存在
invalid_argument 逻辑错误:不合适的参数
length_error 逻辑错误:试图生成一个超出该类型最大长度的对象
out_of_range 逻辑错误:使用一个超出有效范围的值
3、new头文件定义了bad_alloc异常类型,提供因无法分配内存而由new抛出的异常
4、type_info头文件定义了bad_cast异常类型(要使用type_info必须包含typeinfo头文件)
下面是使用异常类的例子:
首先,我定义了几个异常类,这些类也可以从标准异常类进行派生,如下
class BadInitializers
{
public:
BadInitializers() {}
};
class OutOfBounds
{
public:
OutOfBounds(int i) { cout<<"Size "<<i<<" is illegal!!!"<<endl; }
};
class SizeMismatch
{
public:
SizeMismatch() {}
};
然后要在程序中需要的地方使用throw来抛出异常类,两个抛出异常类的例子如下
template <class T>
Array1D<T>::Array1D(int sz)
{
if(sz<0)
{
//throw BadInitializers();
throw invalid_argument("Size has to be bigger than 0!!!");
}
size=sz;
element=new T[size];
}
template <class T>
T &Array1D<T>::operator[](int i) const
{
if(i<0||i>=size)
{
throw OutOfBounds(i);
}
return element[i];
}
然后在主程序中使用try...catch...来捕获异常,并进行相应的处理,如下
try
{
int i=0;
Array1D<int> a1(5);
a1[0]=1;
a1[1]=3;
a1[2]=5;
a1[3]=7;
a1[4]=8;
Array1D<int> a2(a1);
for(i=0;i<a2.Size();i++)
{
cout<<a2[i]<<" ";
}
cout<<endl;
Array1D<int> a3(5);
a3=a1+a2;
cout<<a3;
}
catch(BadInitializers)
{
cout<<"Error:BadInitializers!!!"<<endl;
}
catch(OutOfBounds &e)
{
cout<<"Error:OutOfBounds!!!"<<endl;
}
catch(SizeMismatch &e)
{
cout<<"Error:SizeMismatch!!!"<<endl;
}
catch(invalid_argument &e)
{
cout<<"Error:"<<e.what()<<endl;
}
catch(...)
{
cout<<"An unknown error!!!"<<endl;
}