异常捕获的传参方式

来源:互联网 发布:it人才需求 编辑:程序博客网 时间:2024/06/03 16:37
 
写一个catch子句必须确定让异常通过何种方式传递到catch子句里。可以有三个选择:与给函数传递参数一样,通过指针(by pointer),通过传值(by value)或通过引用(by reference)。

 我们先讨论通过指针方式捕获异常(catch by pointer)。从throw处传递一个异常到catch子句是一个缓慢的过程,在理论上这种方法的实现对于这个过程来说是效率最高的。因为在传递异常信息时,只有采用通过指针抛出异常的方法才能够做到不拷贝对象,例如:
  class exception { ... }; // 来自标准C++库(STL)
                        // 中的异常类层次
                     

  void someFunction()
  {
   static exception ex; // 异常对象
   ...
   throw &ex; // 抛出一个指针,指向ex
   ...
  
  void doSomething()
  {
   try {
    someFunction(); // 抛出一个 exception*
   }
   catch (exception *ex) { // 捕获 exception*;
    ... // 没有对象被拷贝
   }
  }

 这看上去很不错,但是实际情况却不是这样。为了能让程序正常运行,程序员定义异常对象时必须确保当程序控制权离开抛出指针的函数后,对象还能够继续生存。全局与静态对象都能够做到这一点,但是程序员很容易忘记这个约束。如果真是如此的话,他们会这样写代码:
  void someFunction()
  {
   exception ex; // 局部异常对象;
               // 当退出函数的生存空间时
               // 这个对象将被释放。

   ...

  throw &ex; // 抛出一个指针,指向
  ...       // 已被释放的对象
  }

 因为处理这个异常的catch子句接受到的指针,其指向的对象已经不再存在。
 另一种抛出指针的方法是在建立一个堆对象(new heap object):
  void someFunction()
  {
   ...
   throw new exception; // 抛出一个指针,指向一个在堆中
   ...                // 建立的对象(希望
  }                   // 操作符new自己不要再抛出一个异常!)

 这避免了捕获一个指向已被释放对象的指针的问题,但是catch子句又面临一个令人头疼的问题:他们是否应该删除他们接受的指针?如果是在堆中建立的异常对象,那他们必须删除它,否则会造成资源泄漏。如果不是在堆中建立的异常对象,他们绝对不能删除它,否则程序的行为将不可预测。该如何做呢?
 这是不可能知道的。一些clients可能会传递全局或静态对象的地址,另一些可能传递堆中建立的异常对象的地址。通过指针捕获异常,将遇到一个哈姆雷特式的难题:是删除还是不删除?这是一个难以回答的问题。所以最好避开它。
 而且,通过指针捕获异常也不符合C++语言本身的规范。四个标准的异常――bad_alloc(当operator new不能分配足够的内存时,被抛出),bad_cast(当dynamic_cast针对一个引用(reference)操作失败时,被抛出),bad_typeid(当dynamic_cast对空指针进行操作时,被抛出)和bad_exception(用于unexpected异常)――都不是指向对象的指针,所以必须通过值或引用来捕获它们。

 通过值捕获异常(catch-by-value)可以解决上述的问题,例如异常对象删除的问题和使用标准异常类型的问题。但是当它们被抛出时系统将对异常对象拷贝两次。而且它会产生slicing problem,即派生类的异常对象被做为基类异常对象捕获时,那它的派生类行为就被切掉了(sliced off)。这样的sliced对象实际上是一个基类对象:它们没有派生类的数据成员,而且当调用它们的虚拟函数时,系统解析后调用的是基类对象的函数。(当一个对象通过传值方式传递给函数,也会发生一样的情况)。例如下面这个程序采用了扩展自标准异常类的异常类层次体系:
  class exception {                   // 一个标准异常类
  public:                            
   virtual const char * what() throw(); // 返回异常的简短描述.
   ...                                // (在函数声明的结尾处的"throw()")
  };                                

  class runtime_error: //标准C++异常类
  public exception { ... };

  class Validation_error:public runtime_error {
  public:                                    // 客户自己加入个类
   virtual const char * what() throw();  // 重新定义在异常类中虚拟函数
   ...                                
  };

  void someFunction() // 抛出一个 validation异常
  {                
   ...
   if (a validation 测试失败) {
    throw Validation_error();
   }
   ...
  }

  void doSomething()
  {
   try {
    someFunction(); // 抛出 validation异常
   }              

   catch (exception ex) { //捕获所有标准异常类
              // 或它的派生类

    cerr << ex.what(); // 调用 exception::what(),
    ... // 而不是Validation_error::what()
   }
  }

 调用的是基类的what函数,即使被抛出的异常对象是Validation_error和 Validation_error类型,它们已经重新定义的虚拟函数。这种slicing行为绝不是我们所期望的。
 最后剩下方法就是通过引用捕获异常(catch-by-reference)。通过引用捕获异常能避开上述所有问题。不象通过指针捕获异常,这种方法不会有对象删除的问题而且也能捕获标准异常类型。也不象通过值捕获异常,这种方法没有slicing problem,而且异常对象只被拷贝一次。
我们采用通过引用捕获异常的方法重写最后那个例子,如下所示:
  void someFunction() //这个函数没有改变
  {
   ...

   if (a validation 测试失败) {
    throw Validation_error();
   }
   ...
  }

  void doSomething()
  {
   try {
    someFunction(); // 没有改变
   }
   catch (exception& ex) { // 这里,我们通过引用捕获异常
              // 以替代原来的通过值捕获

    cerr << ex.what(); // 现在调用的是
             // Validation_error::what(),
    ... // 而不是 exception::what()
   }
  }
  这里没有对throw进行任何改变,仅仅改变了catch子句,给它加了一个&符号。然而这个微小的改变能造成了巨大的变化,因为catch块中的虚拟函数能够如我们所愿那样工作了:调用的Validation_erro函数是我们重新定义过的函数。
  如果通过引用捕获异常(catch by reference),就能避开上述所有问题,不会为是否删除异常对象而烦恼;能够避开slicing异常对象;能够捕获标准异常类型;减少异常对象需要被拷贝的数目。所以要紧记通过引用捕获异常(Catch exceptions by reference)!
 
 
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 币乎账号被骗了怎么办? q币充了想返还怎么办 q币账号充值错了怎么办 淘宝乐充话费没到账怎么办 微信信用卡还款未到账怎么办 登不上qq怎么改qq密码怎么办 qq改不了以前的密码怎么办 qq微信密码都被改了怎么办 qq账号被盗一直改密码怎么办? 2018qq密码忘了怎么办 我qq密码忘记了怎么办 微信怎么办该改密码 微信改密码收不到验证码怎么办 微信不能改密码怎么办 qq钱包被限额了怎么办? 注册战网手机号被使用怎么办 电信充值卡密码刮花了怎么办 油卡充值卡密码刮花了怎么办 电费充值卡密码刮花了怎么办 手机充值卡密码刮坏了怎么办 办中石化油卡怎么办 移动代充q币没到怎么办 电信手机话费充多了怎么办 微信钱包提现提错银行卡怎么办 qq余额提现不了怎么办 qq钱包充错话费了怎么办 苹果账户扣了钱怎么办 苹果平板冲不进去电怎么办 qq红包输了钱怎么办 qq红包实名认证没有银行卡怎么办 扣扣红包发不了怎么办 qb充错账号了怎么办 q币冲错了号了怎么办 微信qb冲错号码怎么办 微信qb冲错了怎么办 qb冲到小号了怎么办 无意中充了q币怎么办 在绝地求生里打不开充值页面怎么办 电脑版迷你世界打不开怎么办 电脑的腾讯视频打不开怎么办 好多程序连不上网了怎么办