当你写一个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; // 抛出一个指针,指向一个在堆中
 ... // 建立的对象(希望
}
// 自己不要再抛出一个
// 异常!)
  这避免了捕捉一个指向已被释放对象的指针的问题,但是catch子句的作者又面临一个令人头疼的问题:他们是否应该删除他们接受的指针?假如是在堆中建立的异常对象,那他们必须删除它,否则会造成资源泄漏。假如不是在堆中建立的异常对象,他们绝对不能删除它,否则程序的行为将不可猜测。该如何做呢?

  这是不可能知道的。一些clients可能会传递全局或静态对象的地址,另一些可能转递堆中建立的异常对象的地址。通过指针捕捉异常,将碰到一个哈姆雷特式的难题:是删除还是不删除?这是一个难以回答的问题。所以你最好避开它。

  而且,通过指针捕捉异常也不符合C++语言本身的规范。四个标准的异常――bad_alloc(当Operator new(参见条款8)不能分配足够的内存时,被抛出),bad_cast(当dynamic_cast针对一个引用(reference)操作失败时,被抛出),bad_typeid(当dynamic_cast对空指针进行操作时,被抛出)和bad_exception(用于uneXPected异常;参见条款14)――都不是指向对象的指针,所以你必须通过值或引用来捕捉它们。

  通过值捕捉异常(catch-by-value)可以解决上述的问题,例如异常对象删除的问题和使用标准异常类型的问题。但是当它们被抛出时系统将对异常对象拷贝两次(参见条款12)。而且它会产生slicing PRoblem,即派生类的异常对象被做为基类异常对象捕捉时,那它的派生类行为就被切掉了(sliced off)。这样的sliced对象实际上是一个基类对象:它们没有派生类的数据成员,而且当调用它们的虚拟函数时,系统解析后调用的是基类对象的函数。(当一个对象通过传值方式传递给函数,也会发生一样的情况――参见Effective C++ 条款22)。例如下面这个程序采用了扩展自标准异常类的异常类层次体系:

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)!

More Effective C++:通过引用捕获异常的更多相关文章

  1. effective c++:引用传递与值传递,成员函数与非成员函数

    以pass-by-reference-to-const 替换pass-by-value 考虑以下class继承体系 class Person { public: Person(); // parame ...

  2. More Effective C++ (2)

    接下来的是more effective c++ 11至20条款: 11.禁止异常信息(exceptions)传递到析构函数外.析构函数的调用情况可能有两种:(1)对象正常销毁 (2)异常传播过程中的栈 ...

  3. More Effective c++

    指针和引用 引用对象必须存在,即不能引用空值,指针可以指向空值,引用必须初始化指向一个对象 指针可以改变指向的对象,引用不能改变所引用的对象 不改变指向对象使用引用,改变指向对象使用指针 重载[]时必 ...

  4. C++异常处理:try,catch,throw,finally的用法

    写在前面 所谓异常处理,即让一个程序运行时遇到自己无法处理的错误时抛出一个异常,希望调用者可以发现处理问题. 异常处理的基本思想是简化程序的错误代码,为程序键壮性提供一个标准检测机制. 也许我们已经使 ...

  5. C++异常处理: try,catch,throw,finally的用法

    写在前面 所谓异常处理,即让一个程序运行时遇到自己无法处理的错误时抛出一个异常,希望调用者可以发现处理问题. 异常处理的基本思想是简化程序的错误代码,为程序键壮性提供一个标准检测机制. 也许我们已经使 ...

  6. 【VS开发】C++异常处理操作

    异常处理的基本思想是简化程序的错误代码,为程序键壮性提供一个标准检测机制. 也许我们已经使用过异常,但是你会是一种习惯吗,不要老是想着当我打开一个文件的时候才用异常判断一下,我知道对你来说你喜欢用re ...

  7. C++ Knowledge series Inheritance & RTTI & Exception Handling

    Inheritance The pointer or reference to base class can address/be assigned with any of the classes d ...

  8. 《C+编程规范 101条规则、准则与最佳实践》笔记

    <C+编程规范 101条规则.准则与最佳实践> 0.不要拘泥于小节(了解哪些东西不应该标准化) * 与组织内现有编码规范一致即可 * 包括但不限于: - 缩进 - 行长度 - 命名规范 - ...

  9. Effective Java 第三版——43.方法引用优于lambda表达式

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

随机推荐

  1. anroid源码下载和编译

    本文是在Ubuntu10.10系统上进行实践的. 1 因为我们需要Android的模拟器,所以需要安装Android的SDK,并创建AVD, 可以一次命名为AVD15,AVD22,AVD23,... ...

  2. sql的嵌套查询,把一次查询的结果做为表继续进一步查询;内联视图

    Mysql的嵌套表查询 嵌套SELECT语句也叫子查询,一个 SELECT 语句的查询结果能够作为另一个语句的输入值.子查询可以: 出现在Where子句中, 出现在from子句中,作为一个临时表使用, ...

  3. java使用省略号代替多参数(参数类型... 参数名)

    J2SE 1.5提供了“Varargs”机制.借助这一机制,可以定义能和多个实参相匹配的形参.从而,可以用一种更简单的方式,来传递个数可变的实参.本文介绍这一机制的使用方法,以及这一机制与数组.泛型. ...

  4. C++ Explicit Constructors(显式构造函数)

    C++ 为类(Class)提供了许多默认函数.如果自己没有申明,编译器会为我们提供一个copy构造函数.一个copy assignment操作符和一个析构函数.此外,如果没有申明任何构造函数,编译器会 ...

  5. ubuntu下用户的创建、修改

    一.1.添加用户 (1)创建一个新的用户username #sudo useradd username (2)设置用户username 的密码 #sudo passwd username 2.添加用户 ...

  6. BS系统经验总结

    本文章是对刚做完BS系统的总结.主要记录开发过程中遇到的问题,及问题是如何解决的. 1,界面显示 一个系统界面首先要和谐,比如不同页面文本框长度高度要统一,按钮样式要一致,表格显示一样居中都居中靠左都 ...

  7. go语言之进阶篇面向对象编程

    1.面向对象编程 对于面向对象编程的支持Go 语言设计得非常简洁而优雅.因为, Go语言并没有沿袭传统面向对象编程中的诸多概念,比如继承(不支持继承,尽管匿名字段的内存布局和行为类似继承,但它并不是继 ...

  8. 系统出现bootmgr is missing解决方式,戴尔dellserver装系统须要特别注意的问题

    系统出现bootmgr is missing解决方式,戴尔dellserver装系统须要特别注意的问题 欢迎关注http://blog.csdn.net/aaa123524457 转载请注明出处: h ...

  9. magento upsell from cur_category

    <?php /** * Magento * * NOTICE OF LICENSE * * This source file is subject to the Academic Free Li ...

  10. githug-54-git练习

    1-40: http://wiki.jikexueyuan.com/project/git-54-stage-clear/ 41-50: https://blog.csdn.net/maxam0128 ...