一【概述】
在平时的开发过程中,我发现很多程序员都不愿意使用异常类。问他们为什么不用,回答无非有以下几种:1. 使用异常类太麻烦了,没有返回错误码的方式方便;2. 已经习惯C语言中使用以返回错误码的方式来处理异常情况了;3. C++的异常类的代码编写加重了程序员负担,提高了开发成本;4. C++代码中有时需要调用一些C库,而C语言是返回错误码的,所以为了统一风格也就将就着使用返回值了。那么,异常类跟传统的返回错误码的方式相比,有哪些优点?异常类应该怎样设计呢?本文将围绕这两点进行一些思考。
二【C++异常类说明】
我们在平时编写代码时,一般很难将异常的情况全部考虑周全,主观上是因为这取决于开发人员的个人水平、经验或对业务的熟悉程度,不过从客观上来说也是因为错误的场景一般会远远多于正常的处理情况,很难一次就全部想到,尤其是在大型复杂的系统中,只能在日后不断进行修补维护。针对这些问题,C++中引入了异常的概念,目的是用来加强系统在出现异常情况时的处理能力,提高系统的鲁棒性、稳定性和可用性。
- C++异常类与返回值最大的不同在于当它被抛出的时候,它会强迫调用者去处理,否则默认就会终止进程。这是合理的,因为当程序处于一个非正常状态并且不能从中恢复,那就应该马上终止,否则很有可能耗费更多的系统资源甚至造成更大损失。
- C++异常类可以将对同一类异常进行集中处理。这样可以保证良好的代码结构,同时也让你集中精力编写正常的业务逻辑代码。
- 相对于C语言里面一个苍白无力的错误码,C++异常类可以包含丰富的异常信息,如:错误描述、错误码、发生异常时的上下文信息等等。这可以让开发人员更加方便、精准的推断异常的原因,从而更加到位的处理异常。
- 将异常看做一个对象,这也符合面向对象的设计理念。
但是C++的异常机制并没有像JAVA那样纯粹,使得C++异常在使用时可能会导致一些别的错误。这体现在一下几个方面:
- C++的异常规格(exception specification)并不是强制性的,而JAVA则一定会对异常规格进行静态检查。也就是说当C++函数需要抛出异常的时候,你可以选择在函数说明时使用异常规格描述,也可以不使用。因为在C语言里面是没有异常规格的,同时C++又要与C兼容,所以C++无法强制使用异常规格,否则就无法调用C函数了。不过,最致命的还是异常规格有时可能会有实际情况不符,即,程序在运行的时候,函数可能会抛出异常规格中没有的异常类型,这时就会导致进程调用unexcepted()函数。默认情况下,该函数又会调用terminate(),这个函数最终会导致进程终止。这就导致异常规格形同虚设,还可能会误导调用者,所以《C++编程规范:101条规则、准则与最佳实践》中也指出不要使用exception specification,这会导致很多不确定的情况。
- C++异常会导致执行流程的跳转,从而使得一些分配的资源没有得到释放。JAVA中可以通过finally来保证非内存资源得到释放,而C++抛出异常之前必须插入代码来释放相关资源,也可以通过auto_ptr(这个玩意儿本身就有一定的局限性)来自动释放内存资源。
三【C++异常类使用】
一般情况下,异常类的使用需要回答这三个问题:什么时候抛出异常?抛什么样的异常?这个异常需要如何被处理?
对于第一个问题,我们需要理解异常是个什么东西?异常一般是用来报告错误的一种机制。额,可是仔细推敲一下,那么错误又是什么东西呢?错误就是当被调用的函数无法履行开发者预先赋予它的职责时,我们就说该函数出错了。这个时候就可以抛出异常来告诉上层的调用者出错了。接下来,我们就需要考虑应该如何设计这个异常,这也就是第二个问题。
异常的设计需要根据具体的情况。如果异常处理相对复杂,可以将异常类的粒度设计的小一些,调用者可以根据不同的异常类型进行不同的处理,注意,这时异常类的抽象层次要与业务的抽象层次相对应。相反,如果异常处理没那么复杂,则粒度设计的大一些,使得一种异常处理方法可以应付多种异常情况。
一般情况下,我们有两种处理方式:1放弃;2不放弃(貌似说了句废话)。
当我们选择放弃这个出错函数的职责时,该异常就会继续往上抛出,从而系统就会继续栈展开(unwinding stack),直到被系统进入一个正常状态(被捕获),然后记录日志,准备处理下一个事件。这时,如果无法恢复到一个正常状态时,最终则会导致进程终止。另外,当我们选择尝试着采取一些手段来解决这个错误的时候,我们可以根据具体的函数功能进行恢复。比如,当查询某个数据库出错时,我们可以选择重新查询一次,或者选择另一个备用数据库进行查询。
四【资源】
下面是一些关于异常讨论的文章。