C与C++中的异常处理10 (转)

本文探讨了C++中函数try块的使用方法及其解决构造函数异常问题的有效性。通过具体的代码示例,展示了如何利用函数try块捕获并处理构造函数中可能产生的异常。
C与C++中的异常处理10 (转)[@more@]

1.  从私有子对象中产生的异常XML:namespace prefix = o ns = "urn:schemas-microsoft-com:Office:office" />

  几部分来,我一直展示了一些技巧来捕获从对象的构造函数中抛出的异常。这些技巧是在异常从构造函数中漏出来后处理它们。有时,调用者需要知道这些异常,但通常(如我所采用的例程中)异常是从调用者并不关心的私有子对象中爆发的。使得用户要关心“不可见”的对象表明了设计的脆弱。

  在历史上,(可能抛异常)的构造函数的实现者没有简单而健壮的解决方法。看这个简单的例子:

#include

 

class buffer

  {

public:

  explicit buffer(size_t);

  ~buffer();

private:

  char *p;

  };

 

buffer::buffer(size_t const count)

  : p(new char[count])

  {

  }

 

buffer::~buffer()

  {

  delete[] p;

  }

 

static void do_something_with(buffer &)

  {

  }

 

int main()

  {

  buffer b(100);

  do_something_with(b);

  return 0;

  }

 

  buffer的构造函数接受字符数目并从自由空间分配内存,然后初始化buffer::p指向它。如果分配失败,构造函数中的new语句产生一个异常,而buffer的用户(这里是main函数)必须捕获它。

 

1.1  try块

  不幸的是,捕获这个异常不是件容易事。因为抛出来自buffer::buffer,所有buffer的构造函数的调用应该被包在try块中。没脑子的解决方法:

try

  {

  buffer b(count);

  }

catch (...)

  {

  abort();

  }

do_something_with(b); // ERROR. At this point,

  //  'b' no longer exists

是不行的。do_something_with()的调用必须在try块中:

try

  {

  buffer b(100);

  do_something_with(b);

  }

catch (...)

  {

  abort();

  }

//do_something_with(b);

  (免得被说闲话:我知道调用abort()来处理这个异常有些过份。我只是用它做个示例,因为现在关心的是捕获异常而不是处理它。)

  虽然有些笨拙,但这个方法是有效的。接着考虑这样的变化:

static buffer b(100);

 

int main()

  {

//  buffer b(100);

  do_something_with(b);

  return 0;

  }

  现在,b被定义为全局对象。试图将它包入try块

try // um, no, I don't think so

  {

  static buffer b;

  }

catch (...)

  {

  abort();

  }

 

int main()

  {

  do_something_with(b);

  return 0;

  }

将不能被编译。

 

1.2  暴露实现

  每个例子都显示了buffer设计上的基本缺陷:buffer的接口以外的实现细节被暴露了。在这里,暴露的细节是buffer的构造函数中的new语句可能失败。这个语句用于初始化私有子对象buffer::p――一个main函数和其它用户不能操作甚至根本不知道的子对象。当然,这些用户更不应该被要求必须关注这样的子对象抛出的异常。

  为了改善buffer的设计,我们必须在构造函数中捕获异常:

#include

 

class buffer

  {

public:

  explicit buffer(size_t);

  ~buffer();

private:

  char *p;

  };

 

buffer::buffer(size_t const count)

  : p(NULL)

  {

  try

  {

  p = new char[count];

  }

  catch (...)

  {

  abort();

  }

  }

 

buffer::~buffer()

  {

  delete[] p;

  }

 

static void do_something_with(buffer &)

  {

  }

 

int main()

  {

  buffer b(100);

  do_something_with(b);

  return 0;

  }

 

  异常被包含在构造函数中。用户,比如main()函数,从不知道异常存在过,世界又一次清静了。

 

1.3  常量成员

  也这么做?注意,buffer::p一旦被设置过就不能再被改动。为避免指针被无意改动,谨慎的设计是将它申明为const:

class buffer

  {

public:

  explicit buffer(size_t);

  ~buffer();

private:

  char * const p;

  };

很好,但到了这步时:

buffer::buffer(size_t const count)

  {

  try

  {

  p = new char[count]; // ERROR

  }

  catch (...)

  {

  abort();

  }

  }

 

  一旦被初始化,常量成员不能再被改变,即使是在包含它们的对象的构造函数体中。常量成员只能被构造函数的成员初始化列表设置一次。

buffer::buffer(size_t const count)

  : p(new char[count]) // OK

这让我们回到了段落一中,又重新产生了我们最初想解决的问题。

  OK,这么样如何:不用new语句初始化p,换成用内部使用new的辅助函数来初始化它:

char *new_chars(size_t const count)

  {

  try

  {

  return new char[count];

  }

  catch (...)

  {

  abort();

  }

  }

 

buffer::buffer(int const count)

  : p(new_chars(count))

  {

 

//  try

//  {

//  p = new char[count]; // ERROR

//  }

//  catch (...)

//  {

//  abort();

//  }

  }

  这个能工作,但代价是一个额外函数却仅仅用来保护一个几乎从不发生的事件。

 

1.4  函数try块

(WQ注:后面会讲到,function try块不能阻止构造函数的抛异常动作,它其实只起异常过滤的功能!!!见P14.3

  我在上面这些建议中没有发现哪个能确实令人满意。我所期望的是一个语言级的解决方案来处理部分构造子对象问题,而又不引起上面说到的问题。幸运的是,语言中恰好包含了这样一个解决方法。

  在深思熟虑后,C++标准委员会增加了一个叫做“function try blocks”的东西到语言规范中。作为try块的堂兄弟,函数try块捕获整个函数定义中的异常,包括成员初始化列表。不用奇怪,因为语言最初没有被设计了支持函数try块,所以语法有些怪:

buffer::buffer(size_t const count)

try

  : p(new char[count])

  {

  }

catch

  {

  abort();

  }

看起来想是通常的try块后面的{}实际上是划分构造函数的函数体的。在效果上,{}有双重作用,不然,我们将面对更别扭的东西:

buffer::buffer(int const count)

try

  : p(new char[count])

  {

  {

  }

  }

catch

  {

  abort();

  }

  (注意:虽然嵌套的{}是多余的,这个版本能够编译。实际上,你可以嵌套任意重{},直到遇到编译器的极限。)

  如果在初始化列表中有多个初始化,我们必须将它们放入同一个函数try块中:

buffer::buffer()

try

  : p(...), q(...), r(...)

  {

  // constructor body

  }

catch (std::bad_alloc)

  {

  // ...

  }

  和普通的try块一样,可以有任意个异常处理函数:

buffer::buffer()

try

  : p(...), q(...), r(...)

  {

  // constructor body

  }

catch (std::bad_alloc)

  {

  // ...

  }

catch (int)

  {

  // ...

  }

catch (...)

  {

  // ...

  }

 

  古怪的语法之外,函数try块解决了我们最初的问题:所有从buffer子对象的构造函数抛出的异常留在了buffer的构造函数中。

  因为我们现在期望buffer的构造函数不抛出任何异常,我们应该给它一个异常规格申明:

explicit buffer(size_t) throw();

  接着一想,我们应该是个更好点的程序员,于是给我们所有函数加了异常规格申明:

class buffer

  {

public:

  explicit buffer(size_t) throw();

  ~buffer() throw();

  // ...

  };

 

// ...

 

static void do_something_with(buffer &) throw()

 

// ...

 

Rounding Third and Heading for Home

  对我们的例子,最终版本是:

#include

 

class buffer

  {

public:

  explicit buffer(size_t) throw();

  ~buffer() throw();

private:

  char *const p;

  };

 

buffer::buffer(size_t const count)

try

  : p(new char[count])

  {

  }

catch (...)

  {

  abort();

  }

 

buffer::~buffer()

  {

  delete[] p;

  }

 

static void do_something_with(buffer &) throw()

  {

  }

 

int main()

  {

  buffer b(100);

  do_something_with(b);

  return 0;

  }

  用Visual C++编译,自鸣得意地坐下来,看着ide的提示输出。

syntax error : missing ';' before 'try'

syntax error : missing ';' before 'try'

'count' : undeclared identifier

'' : function-style initializer appears

  to be a function definition

syntax error : missing ';' before 'catch'

syntax error : missing ';' before '{'

missing function header (old-style formal list?)

 

  噢!

  Visual C++还不支持函数try块。在我测试过的编译器中,只有Edison Design Group C++ Front End version 2.42认为这些代码合法。

  (顺便提一下,我特别关心为什么编译将第一个错误重复了一下。可能它的计算你第一次会不相信。)

  如果你坚持使用Visual C++,你可以使用在介绍函数try块前所说的解决方法。我喜欢使用额外的new封装函数。如果你认同,考虑将它做成模板:

template

T *new_array(size_t const count)

  {

  try

  {

  return new T[count];

  }

  catch (...)

  {

  abort();

  }

  }

 

// ...

 

buffer::buffer(size_t const count)

  : p(new_array(count))

  {

  }

  这个模板比原来的new_chars函数通用得多,对char以外的类型也有能工作。同时,它有一个隐蔽的异常相关问题,而我将在下次谈到。


来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10752043/viewspace-992216/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/10752043/viewspace-992216/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值