1、异常发生时,异常对象会沿函数调用栈的反方向抛出,这个过程常称为栈展开。

  2、在栈展开过程中,如果异常对象始终都没遇到可行的catch处理块,系统将调用terminate函数强制终止程序。当然如果连try块都没有,系统将直接调用terminate函数。

  3、在栈展开过程中,编译器保证适当的撤销局部对象。每个函数在栈展开退出时,它的局部存储会释放,如果局部对象是类类型,则自动调用对象的析构函数。

  4、析构函数应该从不抛出异常,因为析构函数都是自动调用的,不会自动加上try测试块,因此析构函数中异常的抛出将直接导致系统调用terminate强制退出。在实践中,由于析构函数释放资源,不太可能出现异常,此外标准库类型都保证它们的析构函数不会引发异常。

  5、如果在构造函数中发生异常,则该对象可能只是部分被构造,即使对象只是部分被构造,也要保证将会适当的撤销已构造的成员。

  6、不能不处理异常,异常是足够重要的,使程序不能按正常情况执行的正常事件。不去捕获异常将直接导致程序的强制终止。

  7、catch子句接收的异常类型可以是内置类型,也可以是类类型,也是说我们可以抛出(throw)一个如int的一般类型作为异常对象。

  8、如果catch子句只需了解异常的类型,则可以省去形参名,像这样:catch(runtime_error) {cout<<"runtime error"<

  9、使用异常处理,我们能将问题的检测和问题的解决分离。程序的一部分检测到的问题可以简单的将其抛出(throw),检测部分可以不必了解问题如何处理;程序的另一部分(指调用可能抛出异常的模块实现具体功能的部分)则可以通过try-catch捕获异常,然后处理,这样把异常的处理留给具体功能实现时不失为一个佳的选择。

  10、异常处理中,检测部分抛出对象的类型决定了哪个处理部分的代码被激活,被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置近的那个。

  11、异常对象与catch进行匹配的规则很严格,一般除了以下几种情况外,异常类型必须与catch的说明类型完全匹配:允许非const到const的转换,允许派生类到基类的转换,将数组和函数类型转换为对应的指针。

  12、异常类型匹配将选择第一个找到的可以处理该异常的catch,因此catch子句列表中,特殊的catch必须先出现。而带有因继承而相关的类型的多个catch子句,必须将派生类的处理代码放在基类类型处理代码之前。

  13、throw表达式抛出的异常对象不同于一般的局部对象,局部对象会在局部模块退出时撤销,而异常对象由编译器管理,而且保证驻留在可能被激活的任意catch都可以访问到的空间中。这个由编译器管理的异常对象由throw表达式创建,并被初始化为被抛出表达式的副本,异常对象将传给对应的catch并在完全处理后才撤销。

  14、由于异常抛出时都进行了一次副本拷贝,因此异常对象必须是可以复制的。

  15、抛出一个表达式时,被抛出对象的静态编译时类型将决定异常对象的类型。

  16.抛出指针通常是一个坏主意,因为抛出指针要求在对应处理代码存在的任意地方都存在指针所指向的对象(注意此时throw抛出时复制的是指针本身,不会去复制指针指向的内容);而且如果该指针是指向派生类对象的基类指针,则那个对象将被分割只抛出基类部分(第15条中的静态类型规则)。

  17、基类异常对象可以用于捕获派生类的异常对象,因此如果catch子句处理因继承而相关的类型,它应该将自己的形参定义为引用来激活运行时调用的多态性。

  18、catch可以继续将捕获到的异常抛出,它使用不带表达式的throw语句重新将异常抛出,如:throw;。被重新抛出的异常对象是原来的异常对象,与catch的形参无关(如原来抛出的是派生类Deriver,catch形参是基类Base,则重新抛出后的异常类型是Deriver),当然如果catch形参是引用的话,原来的异常对象可能已被catch修改了。

  19、可以用catch(...){}来捕获所有的异常,catch(...){}经常与重新抛出表达式结合使用,catch(...)完成可做的所有局部工作,然后重新抛出异常。

  20、构造函数包括初始化列表的异常处理:

Foo::Foo(int n)
try:size(n), array(new int[n]) {
      //...
}
catch(const bad_alloc& e){
      //...
}

  这里的函数测试块将初始化列表和函数体中的代码都纳入try块中。