现在的位置: 首页 > 综合 > 正文

读书摘要—《Thinking in C++ 2rd:Vol 2》

2013年11月02日 ⁄ 综合 ⁄ 共 2781字 ⁄ 字号 评论关闭
第七章 异常处理


    通过使用C++提供的异常处理机制:

    1.    编写异常处理代码不再是一项枯燥的工作,而且不会与正常情况下的代码混在一起。
    2.    程序运行时的错误不允许被忽略。

    C++异常处理机制所提供的优势之一就是允许程序员将面临的任务集中在一个地点解决,而在另外一个地点处理可能出现的错误。

    异常处理理论中存在两种基本模型:Termination & Resumption

异常限定符

    当函数抛出不再异常限定符所允许的异常类型时,运行时系统会自动调用unexpected() 。

    实际上,unexpected()的实现是基于函数指针的,因此程序员可以调用set_unexpected()来设置自定义版本的unexpected()函数。
   
    unexpected() 的一个特殊性质是:如果在其执行过程中抛出异常的话,系统将会从导致unexpected()被调用的那个异常被抛出的地点开始搜寻对应的处理函数。


重新抛出异常


    很简单,使用throw关键字,不带任何参数;对于被重新抛出的异常,系统会在更高(外)层次寻找匹配的处理函数。
 

未捕获的异常

    除了不存在匹配的处理函数之外,出现未捕获的异常的另一种可能型如下:

    在当前异常在被对应的处理函数捕获之前,一个新的异常又被抛出——最常见的原因是异常对象的构造函数在执行时产生异常。

    未捕获的异常,应该被视为编程上的错误。

    terminate () & set_terminate ()

    一个可能抛出异常的析构函数,应该被视为存在着设计上的错误——可能导致terminate( )被调用。

Clean up

   
    C++的异常处理机制保证,程序运行时,在离开某个scope时,这个scope中所有成功构造(构造函数未抛出异常)的对象被自动调用析构函数来完成清理工作;换言之,某个对象若在创建时发生异常(构造函数未成功执行完毕),则不会自动调用析构函数。

构造函数

    在编写C++程序时,需要始终在脑子中自我质疑:假如有异常发生的话,当前的资源会被正确清理吗?

    如果构造函数在执行过程中抛出异常的话,那么对应的析构函数是不会被运行时系统自动调用的。

    naked pointer的解决方案——Making Everything An Object

异常匹配


    如果处理函数所捕获的是异常对象的话,异常对象在传递到处理函数时可能会出现"slice"的现象,即其包含的所有派生类信息都被丢失。

    若处理函数捕获的是引用的话,则没有该问题。

    另外需要注意,在捕获函数中,系统是不会执行默认转换的操作的。
 
避免使用异常机制的场合


    1.    不要用于异步事件:这是因为C++中的异常和处理函数位于同一调用栈;也就是说,异常是依赖于scope这一概念的,而异步事件则是由完全独立的代码来处理的。

    2.    不要用于正常的错误处理:如果拥有足够的信息来处理一个错误的话,那这个错误就不应该被当作异常来抛出——能在当前域解决的问题,不应该推卸给更高层的域。

    3.    不要用于实现控制流

    4.    C++并没有强迫在所有场合都使用异常。

    5.    在旧代码中使用采用了异常机制的库时,需要特别注意。


异常的典型应用


    1.    尽可能的使用异常限定符;这需要和自定义版本的unexpected() 配合使用。
    2.    尽可能的利用C++标准库所提供的标准异常类型。
    3.    尽可能的将要抛出的异常类型定义为nested class。
    4.    有效的利用异常的层次化。
    5.    捕获引用,而不是捕获对象
    6.    在构造函数中抛出异常
    7.    不要在析构函数中抛出异常,否则可能会导致调用terminate ( )。
    8.    消除naked pointer
   
开销

      
    在有异常抛出时,将会产生可观的运行时开销;异常机制在设计时的目标之一就是其可以被实现为在未被触发时不会对程序的运行速度产生可观的影响。

    当异常处理结束时,异常对象保证被运行时系统正确的销毁。

   
 

第八章    RTTI


    在异常处理机制被加入C++后,其实现要求运行时了解确切的对象信息;如此,进一步在语言中添加运行时对象信息的特性就变成很简单的事情了。
   
    RTTI与异常一样,都依赖于存放在VTABLE中的类型信息;因此,如果将RTTI用于非多态类型的话,会得到未预期的结果。
       
    C++中的RTTI有两种形式:typeid() 和dynaminc_cast。
       
    typeid()返回与参数类型对应的全局typeinfo对象的引用。
   

细节

    typeid()同样可以作用于内置类型
   
    尽管typeid()同样可以作用于非多态类型,然而这种情况下得到的信息是可疑的;由于VTABLE并不存在,因此typeid()此时会使用静态信息来返回结果
   
    dynamic_cast不仅可检测准确类型,还可以在一个多层的继承结构中,检测到中间类型。
   
    在向中间类型casting这个问题上,typeid()和dynamic_cast是存在区别的;typeid()永远返回对描述精确类型的typeinfo对象的引用,因此它不会返回中间类型的信息。
   
    RTTI无法作用于void pointer; void * 意味着:不存在任何类型信息。
   
   

RTTI中的异常

    当dynamic_cast操作失败时,抛出bad_cast异常;
    当typdid()作用于null pointer时,抛出bad_typeid异常。
   

正确使用RTTI

    不要滥用RTTI,应该首先考虑多态机制。
    如果基类是源于某个库或者是由其他人控制其代码,那么利用RTTI来实现某个特殊派生类的功能扩展,可以作为解决该问题的一种手段。
   

开销

    通常,RTTI的实现是依赖于在VTABLE中增加额外的指针;这个指针指向与某个特定类型对应的typeinfo对象(每个类只有一个对应的typeinfo实例)。
   
    typeid()的机制很简单:利用VPTR来获取typeinfo指针,然后返回对typeinfo对象的引用。该操作具有确定性,总可以确定操作所花费的时间。
   
    而对于dynamic_cast,由于必须检查一系列基类,其开销要大于typeid();此外,其执行时间是不确定的。
       
构建自己的RTTI系统


    本质上说,构建RTTI系统,只需要一个虚函数用来确认对象的确切类型,以及一个将基类指针作为输入而返回派生类指针的cast函数。
 

抱歉!评论已关闭.