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

来源:互联网 发布:小猪cms官网 编辑:程序博客网 时间:2024/04/29 18:15

转自博客园:http://www.cnblogs.com/alephsoul-alephsoul/archive/2012/11/30/2796905.html

 

概述

   今天听了项目组里的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抛出的异常之后,程序就会直接宕掉。所以,不要让异常逃离析构函数,否则,你就悲剧了。

 

原创粉丝点击