1.异常出现的目的
在c++语言的设计和演化中,Bjarne Stroustrup说过异常的设计假定如下情况:
基本上是为了处理错误
与函数定义相比,异常处理是很少的
与函数调用相比,异常出现的频率较少
异常仅仅是语言层次上的概念
同时:
异常不是为了作为另外一种返回机制,而是一种容错机制
不是想把函数都转变成一个容错的试题,而是想作为一种机制,提供给子系统容错能力,即使各个函数在写法上没有关心全局的错误处理
并不是想将设计者都约束到一个正确的错误处理概念上,而是希望语言更有表达能力
所以说异常实际上最早出现我想更多是c++对c一种容错机制的补充,而不是对于返回值的改进,现在很多书上都提倡用异常机制替代返回值,宣称异常机制有很多优点,也有很多人鄙视异常机制,比如zero mq的作者就曾经说过,异常机制无法做到错误处理和引发错误的地方的耦合,我想他们都理解错了异常机制出现的目的
2.异常与返回值判断错误的区别(我自己的理解)
作为一种容错机制,异常并未想要取代返回值检查错误的改进,其中一个最显著的区别就是返回值即使你不是处理,并不会导致程序的崩溃,而异常会,即返回值的默认语义就是:
我这个函数会发生错误
即使我发生错误,你可以无视我的错误
而异常机制的语义就是:
如果我发生错误,你必须处理我这个错误,否则程序就会崩溃
异常机制强制你去处理一些事情,你必须明确的知道我这个函数中,我发生错误,并且我处理了他,
而返回值则没有这种强制性
还有一个比较明显的好处,就是异常可以让错误处理集中在一处处理,比如我们一个socket,在写一个socket的时候发现网络连接断开了,这个时候我们肯定是要重新去连接,如果用C语言的话一般是直接一层一层的用errCode return上去,而且在return的时候我们需要一直去检查状态
比如if(call_function() )
return
这样你会发现你的代码里面有很多的if ... return,但是如果我们在底层使用throw出来,则不用关心那么多 了
所以我建议大家在编写c++代码的时候,要考虑下,如果我这个函数出现了错误,我是否允许一些程序继续往下执行??再来决定是否使用异常还是返回值,以上仅仅是我的个人建议,而不是bjarne stroustrup的建议
3.为什么c++不让用户知道函数会引发什么异常
既然异常机制的语义是如果我发生错误,你必须处理我这个错误,但是在函数的声明上,我们很难看出来这个函数是否需要捕获异常,因此,最早c++的设计团队有这样的想法,就是在函数的声明加上异常抛出的语义
void function(int args) throw e1,e2
这样用户在看到这个函数声明的时候就知道这个函数会抛出什么异常,其实我觉得这样挺方便的,现在我无法知道内部程序会抛什么异常一直是我所困扰的,如果这样,我们就可以在编译的时候知道我们的程序有多少未捕获的异常
假设c++真的实现了异常的静态检查,我们在编译的时候就可以直接检测到代码中的错误,会发生什么问题??
假设我们有三个函数,其中存在不同的三个模块,
分别为function0调用function1,function1调用function2,此时function2抛出异常e1,需要function0处理,function1和function0的模块为了不编译错误,function1必须捕获函数并且把他重新throw,而function0必须处理这个异常
后面,function2增加了异常抛出e2,此时我们为了通过编译,我们需要重新修改function1的代码,并且重新编译function1,这个修改也会影响到function0,于是我们也必须去修改function0,这样,将会导致多余的代码修改以及重编译,这个不是c++的设计团队所希望看到的
所以后来bjarne stroustrup认为这样的静态检查并不是c++所希望的,他更认为这种检查将由另外一种工具来检查更好
我在想这种语法作为一种提示语法也并非不可,只是作为提示用,编译器不会对此进行保证,不过回头想想,这跟注释有什么区别??
4:异常的核心就是资源管理
在异常里面还有一个比较头疼的就是资源管理,bjarne stroustrup也明确指出异常的设计核心实际上是资源的管理,原因在于如果一个程序打开了一个文件,程序在运行过程中抛出了异常,而文件关闭的代码则在程序抛出异常代码的后面,那么则文件无法正常关闭
于是这个时候,RAII就诞生了,我看到这里也感到有点诧异,RAII居然是因为异常诞生的
于是,不管我们是否进行文件关闭操作,只要该函数析构掉,我们的文件总能正常关闭
bjarne stroustrup也提出异常处理给构造函数提供了一种报告出错的直接方法,如果没有异常函数,那么我们将只能迂回地去检测这个函数是否构造完成,与此相反,scotter meyers还是hutter(忘记是谁了,反正是一位c++大神)建议,就是不要在构造函数中抛出异常,因为如果在构造函数里面抛出异常,那个申请的资源可能会泄漏,比如
x = new X()
如果在构造x的过程中抛出异常,那么你就无法删除new X申请的资源,这个也成了很多人嘲笑c++异常的例子
于是乎,很多程序转而另外定义一个init来供外部调用,这个也是我经常在网上看到的建议,于是你经常能看到如下的代码:
x = new X()
try
{
x->init();
}
catch(...)
{
}
但是bjarne stroustrup对对这种做法不赞成(http://www.cise.ufl.edu/~manuel/stroustrup/ex.pdf),
Having a separate init() function is an opportunity to
– Forget to call init()
– Call init() twice
– Forget to test that init() succeeded
– Forget that init() might throw an exception
– Use an object before calling init()
对此,scotter meyers在他的more effective c++的item 10有给出建议,总的来说,还是要遵守不要在构造函数里面对外抛出异常,而是在构造函数内处理异常,并且在构造函数内利用RAII来达到自动管理资源的目的,当然后一条不是必须的,只要你能保证你能够正常释放资源即可,利用RAII只是为了方便而已
5.为什么没有办法从发生异常的地方继续执行
我想这个是很多人一直想要的功能,当时这个问题c++讨论组也讨论了很久,最后从以往的设计角度上来看,这种所谓的唤醒机制都是不靠谱的,因此没有加入,有一个os最早就是朝着这个方向设计的,后来几乎把这部分唤醒功能全部都去掉了,我想估计c++委员会也是基于这样的考虑才不加入这个功能
上面的就差不多是c++异常机制设计的一些思路了,当然这里面参杂了我的一些看法,其实从这里面来看,很多人不喜欢c++是因为他不了解c++一些特性出现的原因,可能他不知道这个在c++里面实际上是错误的用法,只是c++没去禁止你使用而已
最近看<<C++的历史和演化>>感觉还是挺有意思的,了解c++的历史就知道我们在过去有多少错误的c++使用例子了
同时推荐下bjarne stroustrup的文章 Standard-Library Exception Safety
http://www.cise.ufl.edu/~manuel/stroustrup/ex.pdf