C++学习笔记十七-异常

异常处理概述:

通过异常我们能够将问题的检测和问题的解决分离,这样程序的问题检测部分可以不必了解如何处理问题。

C++ 的异常处理中,需要由问题检测部分抛出一个对象给处理代码,通过这个对象的类型和内容,两个部分能够就出现了什么错误进行通信。

 

一、抛出类类型的异常:

      1.异常是通过抛出对象而引发的。该对象的类型决定应该激活哪个处理代码。被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那个。

      2.异常以类似于将实参传递给函数的方式抛出和捕获。异常可以是可传给非引用形参的任意类型的对象,这意味着必须能够复制该类型的对象。

      3.如果抛出一个数组,被抛出的对象转换为指向数组首元素的指针,类似地,如果抛出一个函数,函数被转换为指向该函数的指针

      4.执行 throw 的时候,不会执行跟在 throw 后面的语句,而是将控制从 throw 转移到匹配的 catch,该 catch 可以是同一函数中局部的 catch,也可以在直接或间接调用发生异常的函数的另一个函数中。控制从一个地方传到另一地方,这有两个重要含义:

           A。沿着调用链的函数提早退出。

           B。一般而言,在处理异常的时候,抛出异常的块中的局部存储不存在了。

      5.异常对象由编译器管理,而且保证驻留在可能被激活的任意 catch 都可以访问的空间。异常对象通过复制被抛出表达式的结果创建,该结果必须是可以复制的类型。

 

二、异常对象与继承:

     1.当抛出一个表达式的时候,被抛出对象的静态编译时类型将决定异常对象的类型

     2.在抛出中对指针解引用。对指针解引用的结果是一个对象,其类型与指针的类型匹配。如果指针指向继承层次中的一种类型,指针所指对象的类型就有可能与指针的类型不同。无论对象的实际类型是什么,异常对象的类型都与指针的静态类型相匹配。如果该指针是一个指向派生类对象的基类类型指针,则那个对象将被分割,只抛出基类部分。 

     3.抛出指针通常是个坏主意:抛出指针要求在对应处理代码存在的任意地方存在指针所指向的对象。

 

 

 

三、栈展开

      1.沿嵌套函数调用链继续向上,直到为异常找到一个 catch 子句。只要找到能够处理异常的 catch 子句,就进入该 catch 子句,并在该处理代码中继续执行。当 catch 结束的时候,在紧接在与该 try 块相关的最后一个 catch 子句之后的点继续执行。如果找不到匹配的 catch,程序就调用库函数 terminate

      2.栈展开期间,提早退出包含 throw 的函数和调用链中可能的其他函数。每个函数退出的时候,它的局部存储都被释放,在释放内存之前,撤销在异常发生之前创建的所有对象。如果局部对象是类类型的,就自动调用该对象的析构函数。通常,编译器不撤销内置类型的对象。

      3.栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数。

      4.如果一个块直接分配资源,而且在释放资源之前发生异常,在栈展开期间将不会释放该资源。

      5. 析构函数应该从不抛出异常:在为某个异常进行栈展开的时候,析构函数如果又抛出自己的未经处理的另一个异常,将会导致调用标准库 terminate 函数。一般而言,terminate 函数将调用 abort 函数,强制从整个程序非正常退出。

      6.异常与构造函数:即使对象只是部分被构造了,也要保证将会适当地撤销已构造的成员。类似地,在初始化数组或其他容器类型的元素的时候,也可能发生异常,同样,也要保证将会适当地撤销已构造的元素。

 

 

四、捕获异常:

      1.异常说明符:catch 子句中的异常说明符看起来像只包含一个形参的形参表,异常说明符是在其后跟一个(可选)形参名的类型名。说明符的类型决定了处理代码能够捕获的异常种类。类型必须是完全类型,即必须是内置类型或者是已经定义的程序员自定义类型。类型的前向声明不行。

      2.当 catch 为了处理异常只需要了解异常的类型的时候,异常说明符可以省略形参名;

      3.进入 catch 的时候,用异常对象初始化 catch 的形参。像函数形参一样,异常说明符类型可以是引用。异常对象本身是被抛出对象的副本。是否再次将异常对象复制到 catch 位置取决于异常说明符类型。

      4.如果 catch 子句处理因继承而相关的类型的异常,它就应该将自己的形参定义为引用。

 

五、查找匹配的处理代码:

      1.在查找匹配的 catch 期间,找到的 catch 不必是与异常最匹配的那个 catch,相反,将选中第一个找到的可以处理该异常的 catch。因此,catch 子句列表中,最特殊的 catch 必须最先出现。

           带有因继承而相关的类型的多个 catch 子句,必须从最低派生类类到最高派生类型排序。

      2.异常与 catch 异常说明符匹配的规则比匹配实参和形参类型的规则更严格,大多数转换都不允许——除下面几种可能的区别之外,异常的类型与 catch 说明符的类型必须完全匹配:

      3. 允许从非 const 到 const 的转换。也就是说,非 const 对象的 throw 可以与指定接受 const 引用的 catch 匹配。

允许从派生类型型到基类类型的转换。

      5.将数组转换为指向数组类型的指针,将函数转换为指向函数类型的适当指针。

      6.在查找匹配 catch 的时候,不允许其他转换。具体而言,既不允许标准算术转换,也不允许为类类型定义的转换。

 

六、重新抛出:

       1.有可能单个 catch 不能完全处理一个异常。在进行了一些校正行动之后,catch 可能确定该异常必须由函数调用链中更上层的函数来处理,catch 可以通过重新抛出将异常传递函数调用链中更上层的函数。重新抛出是后面不跟类型或表达式的一个 throw:

    throw;

       2.空 throw 语句将重新抛出异常对象,它只能出现在 catch 或者从 catch 调用的函数中。如果在处理代码不活动时碰到空 throw,就调用 terminate 函数。

       3.虽然重新抛出不指定自己的异常,但仍然将一个异常对象沿链向上传递,被抛出的异常是原来的异常对象,而不是 catch 形参。当 catch 形参是基类类型的时候,我们不知道由重新抛出表达式抛出的实际类型,该类型取决于异常对象的动态类型,而不是 catch 形参的静态类型。例如,来自带基类类型形参 catch 的重新抛出,可能实际抛出一个派生类型的对象。

       4. 一般而言,catch 可以改变它的形参。在改变它的形参之后,如果 catch 重新抛出异常,那么,只有当异常说明符是引用的时候,才会传播那些改变。

 

 

七、捕获所有异常 :即使函数不能处理被抛出的异常,它也可能想要在随抛出异常退出之前执行一些动作。除了为每个可能的异常提供特定 catch 子句之外,因为不可能知道可能被抛出的所有异常,所以可以使用捕获所有异常 catch 子句的。捕获所有异常的 catch 子句形式为 (...)。例如:

     // matches any exception that might be thrown
     catch (...) {
         // place our code here
     }

 

 

 

八、函数测试块与构造函数:关键字 try 出现在成员初始化列表之前,并且测试块的复合语句包围了构造函数的函数体。catch 子句既可以处理从成员初始化列表中抛出的异常,也可以处理从构造函数函数体中抛出的异常。

template <class T> Handle<T>::Handle(T *p)
    try : ptr(p), use(new size_t(1))
    {
         // empty function body
    }  catch(const std::bad_alloc &e)
           { handle_out_of_memory(e); }

构造函数要处理来自构造函数初始化式的异常,唯一的方法是将构造函数编写为函数测试块。

 

 

九、异常类层次:exception 类型所定义的唯一操作是一个名为 what 的虚成员,该函数返回 const char* 对象,它一般返回用来在抛出位置构造异常对象的信息。因为 what 是虚函数,如果捕获了基类类型引用,对 what 函数的调用将执行适合异常对象的动态类型的版本。

 

十、用类管理资源分配:

        1.通过定义一个类来封闭资源的分配和释放,可以保证正确释放资源。这一技术常称为“资源分配即初始化”,简称 RAII。

应该设计资源管理类,以便构造函数分配资源而析构函数释放资源。

     2.auto_ptr 类:auto_ptr 类是接受一个类型形参的模板,它为动态分配的对象提供异常安全

     3.auto_ptr 类设计原则:在任意时刻只有一个 auto_ptrs 对象保存给定指针,如果两个 auto_ptrs 对象保存相同的指针,该指针就会被 delete 两次。

     4.auto_ptr 只能用于管理从 new 返回的一个对象,它不能管理动态分配的数组。

         5.不要使用 auto_ptr 对象保存指向静态分配对象的指针,否则,当 auto_ptr 对象本身被撤销的时候,它将试图删除指向非动态分配对象的指针,导致未定义的行为。

         6.当 auto_ptr 被复制或赋值的时候,有不寻常的行为,因此,不能将 auto_ptrs 存储在标准库容器类型中。

      7.auto_ptr 类是接受单个类型形参的模板,该类型指定 auto_ptr 可以绑定的对象的类型,因此,可以创建任何类型的 auto_ptrs

      8.auto_ptr<string> ap1(new string("Brontosaurus"));

如果调用该 auto_ptr 对象已经保存的同一指针的 reset 函数,也没有效果,不会删除对象。

 

 

十一、定义异常说明:

      1.异常说明跟在函数形参表之后。一个异常说明在关键字 throw 之后跟着一个(可能为空的)由圆括号括住的异常类型列表:

     void recoup(int) throw(runtime_error);

        这个声明指出,recoup 是接受 int 值的函数,并返回 void。如果 recoup 抛出一个异常,该异常将是 runtime_error 对象,或者是由 runtime_error 派生的类型的异常。

      2.空说明列表指出函数不抛出任何异常:

     void no_problem() throw();

      3.如果一个函数声明没有指定异常说明,则该函数可以抛出任意类型的异常。异常说明是函数接口的一部分,函数定义以及该函数的任意声明必须具有相同的异常说明。

      4.违反异常说明:不可能在编译时知道程序是否抛出异常以及会抛出哪些异常,只有在运行时才能检测是否违反函数异常说明。

如果函数抛出了没有在其异常说明中列出的异常,就调用标准库函数 unexpected。默认情况下,unexpected 函数调用 terminate 函数,terminate 函数一般会终止程序。

       5.在编译的时候,编译器不能也不会试图验证异常说明。

       6.确定函数不抛出异常:因为不能在编译时检查异常说明,异常说明的应用通常是有限的。

        编译器可以知道合成析构函数将遵守不抛出异常的承诺。

        异常说明有用的一种重要情况是,如果函数可以保证不会抛出任何异常。

       确定函数将不抛出任何异常,对函数的用户和编译器都有所帮助:知道函数不抛出异常会简化编写调用该函数的异常安全的代码的工作,我们可以知道在调用函数时不必担心异常,而且,如果编译器知道不会抛出异常,它就可以执行被可能抛出异常的代码所抑制的优化。

       7.异常说明与虚函数:基类中虚函数的异常说明,可以与派生类中对应虚函数的异常说明不同。但是,派生类虚函数的异常说明必须与对应基类虚函数的异常说明同样严格,或者比后者更受限。这个限制保证,当使用指向基类类型的指针调用派生类虚函数的时候,派生类的异常说明不会增加新的可抛出异常。

        8.派生类不能在异常说明列表中增加异常,原因在于,继承层次的用户应该能够编写依赖于该说明列表的代码。如果通过基类指针或引用进行函数调用,那么,这些类的用户所涉及的应该只是在基类中指定的异常。

        9.通过派生类抛出的异常限制为由基类所列出的那些,在编写代码时就可以知道必须处理哪些异常。代码可以依赖于这样一个事实:基类中的异常列表是虚函数的派生类版本可以抛出的异常列表的超集。

 

 

函数指针的异常说明

        10.异常说明是函数类型的一部分。这样,也可以在函数指针的定义中提供异常说明:

     void (*pf)(int) throw(runtime_error);

这个声明是说,pf 指向接受 int 值的函数,该函数返回 void 对象,该函数只能抛出 runtime_error 类型的异常。如果不提供异常说明,该指针就可以指向能够抛出任意类型异常的具有匹配类型的函数。

        11.在用另一指针初始化带异常说明的函数的指针,或者将后者赋值给函数地址的时候,两个指针的异常说明不必相同,但是,源指针的异常说明必须至少与目标指针的一样严格。

     void recoup(int) throw(runtime_error);
     // ok: recoup is as restrictive as pf1
     void (*pf1)(int) throw(runtime_error) = recoup;
     // ok: recoup is more restrictive than pf2
     void (*pf2)(int) throw(runtime_error, logic_error) = recoup;
     // error: recoup is less restrictive than pf3
     void (*pf3)(int) throw() = recoup;
     // ok: recoup is more restrictive than pf4
     void (*pf4)(int) = recoup;
posted @ 2012-08-10 09:55  ForFreeDom  阅读(365)  评论(0编辑  收藏  举报