[C++基础]039_C++异常处理初级出门+中级进阶

概述

   今天听了项目组里的C++高手讲C++的异常,受益匪浅。果然,与高手一起才能学习到更多的东西。下面我就把这位高手介绍的C++异常处理分享给园子里的博友们。

什么是异常呢?

   在编程语言里,按照出现错误的时机来区分,有编译期错误和运行期错误之分。

编译期错误大家肯定很熟悉了,当我们build一个程序时,console里出现的那些error提示就是编译期错误。这些错误是在编译期就能被编译器检查出来。

运行期错误大家可能不太经常见,因为自己写的程序往往都是在“温室”里运行的,很少看到程序崩溃的情况。运行期错误的种类很多,举个例子,当我们要在堆上申请内存的时候,内存空间不足,或者在创建文件的时候,磁盘空间不足,这些都是运行期错误。我们用一个名字——异常来称呼这样的运行期错误。

C++如何捕获异常?

   C++有自己异常处理体系,它捕获异常的语法与java和C#很相似,可以看下面的代码:

 1 int main(){
 2 
 3     try{
 4         cout<<"This an easy exception example."<<endl;
 5         throw 1;
 6     }catch(int i){
 7         cout<<"Catched the exception: i = "<<i<<endl;
 8     }
 9 
10     return 0;
11 }

语法很简单,用try block来包含要捕获异常区域的代码,这段代码里会有throw语句抛出异常,再用catch来捕获异常,上面代码的输出结果如下:

This an easy exception example.
Catched the exception: i = 1
请按任意键继续. . .

慎用catch(...)

   C++的异常捕获提供了一个万能捕获器,就是catch(...),它可以捕获任意的异常,可以看出来,因为没有参数名,这样我们就没办法获取异常传递过来的内容了。不过还有一个重要的问题,就是catch(...)的位置问题。下面看一段代码:

1     try{
2         cout<<"This an easy exception example."<<endl;
3         throw 1;
4     }catch(...){
5         cout<<"Catched the exception."<<endl;
6     }catch(int i){
7         cout<<"Catched the exception: i = "<<i<<endl;
8     }

上面的代码把catch(...)放到了catch(int i)之前,这样有什么问题呢?catch(int i)包含的异常处理块永远不会被执行。强悍一点的编译器会为我们指出错误,但是有些编译器就没那么强大了。所以,记住一条准则:catch(...)永远放到所有捕获catch处理的最后一个。

慎用继承体系里的类作为catch的参数

   这个问题跟上面的问题类似,也是catch的优先级问题,看下面的代码:

1     try{
2         DerivedClass dc;
3         cout<<"This an easy exception example."<<endl;
4         throw dc;
5     }catch(SuperClass s){
6         cout<<"Catched the exception:SuperClass."<<endl;
7     }catch(DerivedClass d){
8         cout<<"Catched the exception:DerivedClass."<<endl;
9     }

上面的代码中,我们抛出了DerivedClass类的对象,本以为会进入catch(DerivedClass d)的处理块的,但是事实上它之调用了catch(SuperClass s)的处理块。这个代码编译器不会去检查,只能靠我们自己把握了,记住一个准则:要把最高级别的父类放到最后一个Catch里处理。

对象析构函数被调用的三种场合?

   对象析构函数什么时候会被调用呢?这里先说一下,有三种情况析构函数被调用。哪三种呢?先看我们熟悉的两种:

1 void func(){
2     A a;
3 }

第一种:上面的函数在调用时,函数完成调用后,会自动调用A的对象a的析构函数。

1     A *a = new A();
2     delete a;

第二种:显示的调用delete语句也会调用对象的析构函数。

那第三种是什么呢?其实你已经看到了,就是异常处理区域的throw语句,看下面的代码:

1     try{
2         DerivedClass dc;
3         cout<<"This an easy exception example."<<endl;
4         throw dc;
5     }

上面的代码,throw语句实际上为我们调用了一次析构函数,尽管这个函数后面可能还有语句。实际上在抛出一个对象的时候,异常体系已经复制了一个tem_dc的对象。然后再调用DerivedClass的析构函数。所以,下面的代码让我们感到很恼怒:

1     try{
2         DerivedClass *pDc = new DerivedClass();
3         throw pDc;
4     }

我们希望把pDc指向的对象throw出来,实际上我们只是抛出了pDc这个指针,而这个对象早已经被析构掉了。所以这里记住一个准则:尽量不要抛出指针和引用。

不要在异常处理体系中寄希望与类型转换

  不要期望异常处理体系为我们完成类型转换,看下面的代码:

1     try{
2         throw 'a';
3     }catch(int ch){
4         cout<<"this is a ch"<<endl;
5     }

平时写代码的时候,'a'是可以转换成asic码值的,但是这个时候就不行了,程序运行期是错误的。所以,记住一个准则:不要寄希望于C++异常处理体系会帮你做类型转换。

有C++异常处理体系捕获不到的东西吗?

   有的,但也没有,什么意思呢。本来有的,后来被解决了。不知道你还记不记得住C++的成员初始化列表,不记得的话,咱们看下面的代码:

 1 class Test{
 2 private:
 3     int age;
 4 public:
 5     Test():age(initialze(1)){
 6     
 7     }
 8     int initialze(int i){
 9         if(i == 1){
10             throw 1;
11         }else{
12             return 1;
13         }
14     }
15 };

上面的代码中,构造函数在成员初始化列表中调用了可能抛出异常的initialize函数,这样的异常怎么捕获呢?用前辈的一句话:C++于是提供了一种很丑的语法来解决这个问题。怎么解决的呢?看下面的代码:

1     Test()
2     try:age(initialze(1)){
3         {
4             //函数体
5         }
6     }catch(int i){
7         cout<<"exception"<<endl;
8     }

是不是很丑陋的语法,我也觉得怎么好看,不过确实捕获了成员初始化列表的异常。

set_unexpected函数的用处

   这个函数的作用,简而言之,就是设置默认异常处理函数。什么意思呢?看下面的代码就了解了。

 1 void myFunc(){
 2     cout<<"set_unexpected Exception."<<endl;
 3     throw 0;
 4 }
 5 void fun(int x) throw(char)
 6 {
 7     throw 'a';
 8 }
 9 int main(){
10     set_unexpected(myFunc);
11     try{
12         fun(1);
13     }catch(int i){
14         cout<<"int exception"<<endl;
15     }
16 
17     system("pause");
18     return 0;
19 }

从main开始看,我们注册了一个默认异常处理函数,这个函数会对异常做一个修正。fun函数里抛出char的异常,我们的语句是捕获不了的,所以经过默认处理函数修正之后,就可以用catch(int i)捕获了。

不过,上面的代码在VS上运行是不行的,linux下运行就okay了。

Effective C++:不要让异常逃离析构函数

    看懂了上面的,你就已经很厉害啦,当然这只是个玩笑。EffiectiveC++里有一条:不要让异常逃离析构函数。什么意思呢?就是当我们遇到下面这样的代码后:


1
try{ 2 DerivedClass dc; 3 cout<<"This an easy exception example."<<endl; 4 throw dc; 5 }catch(SuperClass s){ 6 cout<<"Catched the exception:SuperClass."<<endl; 7 }catch(DerivedClass d){ 8 cout<<"Catched the exception:DerivedClass."<<endl; 9 }

上面的代码throw dc之后,会调用DerivedClass的析构函数,这样的话,如果析构函数再抛出异常,我们的捕获函数就悲剧的不知道该如何处理了。也就是说,当同时出现两个throw抛出的异常之后,程序就会直接宕掉。所以,不要让异常逃离析构函数,否则,你就悲剧了。

posted @ 2012-11-30 23:29  邵贤军  阅读(2167)  评论(11编辑  收藏  举报