C++ Primer 笔记15

1、通过异常我们能够将问题的检测和问题的解决分离,这样程序的问题检测部分可以不必了解如何处理问题。
Sales_item operator +(const Sales_item& lhs, const Sales_item& rhs)
{
  if(!lhs.same_isbn(rhs))
        throw runtime_error("Data must refer to same ISBN");
  Sales_item ret(lhs);
  ret += rhs;
  return ret;
}
Sales_item item1, item2, sum;
while(cin>>item1>>item2)
{
  try{
      sum = item1 + item2;
  }catch(const runtime_error &e)
  {
        cerr<<e.what()<<"Try again.\n"<<endl;
  }
}
2、异常是通过 抛出(throw)对象而引发的。该对象的类型决定应该激活哪个处理代码。被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那个。
    执行throw的时候,不会执行跟着throw后面的语句,而是将控制从throw转移到匹配的catch,该catch可以是同一函数中局部的catch,也可以在直接或间接调用发生异常的函数的另一个函数。 控制从一个地方传到另一个地方,这有两个重要含义。
① 沿着调用链的函数提早推出
② 一般而言,在处理异常的时候,抛出异常的块中的局部存储不存在了
    因为在处理异常的时候会释放局部存储,所以被抛出的对象就不能再局部存储,而是用throw表达式初始化一个成为 异常对象(exception object)的特殊对象。异常对象由编译器管理,而且保证驻留在可能被激活的任意catch都可以访问的空间。
3、 当抛出一个表达式的时候,被抛出对象的静态编译时类型将决定异常对象的类型。 无论对象的实际类型是什么,异常对象的类型都与指针的静态类型相匹配。如果该指针式一个指向派生类对象的基类类型指针,则那个对象将被分割,只抛出基类部分。
    抛出指针通常是个坏主意:抛出指针要求在对应处理代码存在的任意地方存在指针所指向的对象。
4、首先检查throw本身是否在try块内部,如果是,检查与该try相关的catch字句,看是否其中之一与被抛出对象相匹配。如果找到匹配的catch,就处理异常,如果找不到,就退出当前函数,并且继续在调用函数中查找。
   栈展开(stack unwinding),沿嵌套函数调用链继续向上,直至为异常找到一个catch字句。  栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数。  在为某个异常进行栈展开的时候,析构函数入股又抛出自己的未经处理的另一个异常,将会导致调用标准库 terminate函数。一般而言,terminate函数将调用 abort函数,强制从整个程序非正常退出。  不能不处理异常,如果找不到匹配的catch,程序就调用库函数terminate。
5、catch子句中的 异常说明符(exception specifier)看起来像只包含一个形参的形参表,说明符的类型决定了处理代码能够捕获的异常种类。类型必须是完全类型,即必须是内置类型或者是已经定义的程序员自定义类型。类型的前向声明不行。
   在查找匹配的catch期间,找到的catch不必是与异常最匹配的那个catch,相反,将选中第一个找到的可以处理异常的catch。 通常,如果catch子句处理因继承而相关的类型的异常,它就应该将自己的形参定义为引用。 带有因继承而相关的类型的多个catch子句,必须从最低派生类型到最高派生类型排序。
    如果异常说明符不是引用,则catch对象时异常对象的副本,如果catch对象是基类类型对象而异常对象时派生类型的,就将异常对象分割为它的基类子对象。
6、有可能单个catch不能完全处理一个异常,在进行了一些校正行动之后,catch可能确定该异常必须由函数调用链中更上层的函数来处理,catch可以通过 重新抛出(rethrow)将异常传递给函数调用链中更上层的函数。重新抛出是后面不跟类型或表达式的一个throw: throw;  被抛出的异常是原来的异常对象,而不是catch形参。当catch形参是基类类型的时候,我们不知道由重新抛出表达式抛出的实际类型,该类型取决于异常对象的动态类型,而不是catch形参的静态类型。例如,来自带基类类型形参catch的重新抛出,可能实际上抛出一个派生类型的对象。 
    一般而言,catch可以改变它的形参,在改变它的形参之后,如果catch重新抛出异常,那么,只有当异常说明符是引用的时候,才会传播那些改变。
7、因为不知道可能被抛出的所有异常,所以可以使用 捕获所有异常catch子句的。捕获所有异常的catch子句形式为(...)。例如:
catch(...){
          // place our code here
  捕获所有异常的catch子句与任意类型的异常都匹配。
    如果catch(...)与其他catch子句结合使用,它必须是最后一个,否则,任何跟在它后面的catch子句都将不能被匹配。
8、为了处理来自构造函数初始化式的异常,必须将构造函数编写为 函数测试块(function try block)
例:  template<typename T>
          Handle<T>::Handle(T *p) try : ptr(p),use(new size_t(1))
          {...}catch(const std::bad_alloc &e){handle_out_of_memory(e);}
    关键字try出现在成员初始化列表之前,并且测试块的复合语句包围了构造函数的函数体。catch子句既可以处理从成员初始化列表中抛出的异常,也可以处理从构造函数函数体中抛出的异常。
    构造函数要处理来自构造函数初始化式的异常, 唯一的方法是将构造函数编写为函数测试块。
9、异常类层次
exception
    bad_cast
    runtime_error
              overflow_error
              underflow_error
              range_error
    logic_error
              domin_error
              invalid_argument
              out_of_range
              length_error
    bad_alloc
exception类型所定义的唯一操作是一个名为what的虚函数,该函数返回const char*对象,它一般返回用来在抛出位置构造异常对象的信息。
10、 异常安全(exception safe) 意味着,即使发生异常,程序也能正确操作。在这种情况下,“安全”来自于保证“如果发生异常,被分配的任何资源都适当地释放”。 通过定义一个类来封装资源的分配和释放,可以保证正确的释放资源。这一技术常称为“资源分配即初始化”,简称 RAII
      可能存在异常的程序以及分配资源的程序应该使用类来管理那些资源,使用类管理分配和回收可以保证如果发生异常就释放资源。
11、标准库的 auto_ptr类是异常安全的“资源分配即初始化”技术的例子。auto_ptr类在头文件memory中定义。
    auto_ptr只能用于管理从new返回的一个对象,它不能管理动态分配的数组,不能将auto_ptr存储在标准库容器类型中。 当auto_ptr对象指向一个对象的时候,可以说它“拥有”该对象,当auto_ptr对象超出作用域或者另外撤销的时候,就自动回收auto_ptr所指向的动态分配对象。  通过常规指针分配内存,而且在执行delete之前发生异常,就不会自动释放该内存,如果使用一个auto_ptr对象来代替,将会自动释放内存,即使提早退出这个块也是这样。 在最常见的情况下,将auto_ptr对象初始化为由new表达式返回的对象的地址:
auto_ptr<int> pi(new int(1024)); 
    接受指针的构造函数为explicit构造函数,所以必须使用初始化的直接形式来创建auto_ptr对象。
    当复制auto_ptr对象或者将它的值赋给其他auto_ptr对象的时候,将基础对象的所有权从原来的auto_ptr对象转给副本,原来的auto_ptr对象重置为未绑定状态。 原来的auto_ptr对象不指向对象而新的auto_ptr对象(左边的auto_ptr)拥有基础对象。
    auto_ptr<string> ap3(new string("Pterrodacty1"));
    ap3 = ap2;
将ap2赋给ap3之后:
  删除了ap3指向的对象;
  将ap3置为指向ap2所指的对象;
  ap2是未绑定的auto_ptr对象。
因为复制和赋值是破坏性操作,所以 不能将auto_ptr对象存储在标准容器中。要测试auto_ptr对象,必须使用它的get成员,该成员返回包含在auto_ptr对象中的基础指针。
  if(p_auto.get())
        *p_auto = 1024;
auto_ptr对象与内置指针的另一个区别是,不能直接将一个地址(或者其他指针)赋给auto_ptr对象:
      p_auto = new int(1024);//error:can't assign a pointer to an auto_ptr
相反,必须调用reset函数来改变指针
      if(p_auto.get())
            *p_auto = 1024;
      else
            p_auto.reset(new int(1024));
12、 异常说明(exception specification) 指定,如果函数抛出异常,被抛出的异常将是包含在该说明中的一种,或者是从列出的异常中派生的类型。
      异常说明跟着函数形参表之后,一个异常说明在关键字throw之后跟着一个(可能为空的)由圆括号括住的异常类型列表:
      void recoup(int) throw (runtime_error);
    空说明列表指出函数不抛出任何异常。 异常说明是函数接口的一部分,函数定义以及该函数的任意声明必须具有相同的异常说明。 如果一个函数声明没有指定异常说明,则该函数可以抛出任意类型的异常。
      如果函数跑出了没有在其异常说明中列出的异常,具调用标准库函数 unexpected。默认情况下,unexpected函数调用terminate函数,terminate函数一般会终止程序。
      在const成员函数声明中,异常说明跟着const限定符之后。基类中虚函数的异常说明,可以与派生类中对应虚函数的异常说明不同。 但是,派生类虚函数的异常说明必须与对应基类虚函数的异常说明同样严格,或者比后者更受限。
      异常说明是函数类型的一部分。这样,也可以在函数指针的定义中提供异常说明:
      void (*pf)(int) throw(runtime_error); 源指针的异常说明必须至少与目标指针的一样严格。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值