ITPub博客

首页 > 应用开发 > IT综合 > C与C++中的异常处理15 (转)

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

原创 IT综合 作者:worldblog 时间:2007-12-12 15:16:06 0 删除 编辑
C与C++中的异常处理15 (转)[@more@]

1.  模板安全(续)XML:namespace prefix = o ns = "urn:schemas-microsoft-com:Office:office" />

  在异常安全的第二部分,我讲了在构造函数和析构函数中导致资源泄漏的问题。这次将探索另外两个问题。并且以推荐读物列表结束。

1.1  Problem #2:get

  上次,我定义X::get()为:

T get()

  {

  return *value_;

  }

  这个定义有点小小的不足。既然get()不改变wrapper对象,我应该将它申明为const成员的:

T get() const

  {

  return *value_;

  }

  get()返回了一个T的临时对象。这个临时对象通过T的拷贝构造函数根据*value_隐式生成的,而这个构造函数可能抛异常。要避开这点,我们应该将get()修改为不返回任何东西:

void get(T &value) const throw()

  {

  value = *value_;

  }

  现在,get()接受一个事先构造好的T对象的引用,并通过引用“返回”结果。因为get()现在不调用T的构造函数了,它是异常安全的了。

  真的吗?

  很不幸,答案是“no”。我们只是将一个问题换成了另外一个问题而已,因为语句

value = *value_;

实际上是

value.operator=(*value_);

而它可能抛异常。更完备的解决方法是

void get(T &value) const throw()

  {

  try

  {

  value = *value_;

  }

  catch (...)

  {

  }

  }

  现在,get()不会将异常漏出去了。

  不过,工作还没完成。在operator=给value赋值时抛异常的话,value将处于不确定状态。get()想要有最大程度的健壮接口的话,它必须两者有其一:

l  value根据*value_进行了完全设置,或

l  value没有被改变。

  这两条要将我们弄跳起来了:无论我们用什么方法来解决这个问题,我们都必须调用operator=来设置value,而如果operator=抛了异常,value将只被部分改变。

  我们的这个强壮接口看起来美却不实在。我们无法简单地实现它,只能提供一个弱些的承诺了:

l  value根据*value_进行了完全设置,或

l  value处于一个不确定的(错误)状态。

  但还有一个问题没解决:让调用者知道回传的value是否是“好的”。一个可能的解决方法(也很讽刺的)是抛出一个异常。另外一个可能方法,也是我在这儿采用的方法是返回一个错误码。

  修改后的get()是:

bool get(T &value) const throw()

  {

  bool error(false);

  try

  {

  value = *value_;

  }

  catch (...)

  {

  error = true;

  }

 

  return error;

  }

  提供了一个较弱的承诺的这个新接口是安全的。它行为安全吗?是的。wrapper所拥有的唯一资源是分配给*value_的内存,而它是受保护的,即使operator=抛了异常。

  符合最初的说明,get()有了一个健壮的异常安全承诺,即使T没有这个承诺。最终,我们过于加强了get()的承诺(这取决于value),而应该将它降低到T的承诺层次。我们用一个警告修正get()的承诺,基于我们不能控制或不能预知T的状态。In the end, we over-committed get's guarantee (the deteRminism of value), and had to bring it down to T's level. We amended get's contract with a caveat, based on conditions in T we couldn't control or predict.

  原则:程序的健壮性等于它最弱的承诺。尽可能提供最健壮的承诺,同时在行为和接口上。

  推论:如果你自己的接口的承诺比其他人的接口健壮,你通常必须将你的接口减弱到相匹配的程度。

 

1.2  Problem #3:set

  我们现在的X::set()的实现是:

void set(T const &value)

  {

  *value_ = value;

  }

 

  (和get()不同,set()确实修改wrapper对象,所以不能申明为cosnt。)

  语句

*value_ = value;

应该看起来很熟悉:她只是前面Problem #2中提到的语句

value = *value_;

的反序。注意到这个变化,Problem #3的解决方案就和Problem #2的一样了:bool set(T const &value) throw()

  {

  bool error(false);

  try

  {

  *value = value_;

  }

  catch (...)

  {

  error = true;

  }

  return error;

  }

 

  和我们在get()中回传value遇到的问题一样:如果operator=抛了异常,我们无法知道*value_的状态。我们对get()的承诺的警告在这儿同样适用。

  get()和set()现在有这同样的操作但不同的用途:get()将当前对象的值赋给另外一个对象,而set()将另外一个对象的值赋给当前对象。由于这种对称性,我们可以将共同的代码放入一个assign()函数:

static bool assign(T &to, T const &from) throw()

  {

  bool error(false);

  try

  {

  to = from;

  }

  catch (...)

  {

  error = true;

  }

  return error;

  }

 

  使用了这个辅助函数后,get()和set()缩短为

bool get(T &value) const throw()

  {

  return assign(value, *value_);

  }

 

bool set(T const &value) throw()

  {

  return assign(*value_, value);

  }

 

1.3  最终版本

  wrapper的最终版本是

template

class wrapper

  {

public:

  wrapper() throw()

  : value_(NULL)

  {

  try

  {

  value_ = new T;

  }

  catch (...)

  {

  }

  }

  ~wrapper() throw()

  {

  try

  {

  delete value_;

  }

  catch (...)

  {

  operator delete(value_);

  }

  }

  bool get(T &value) const throw()

   {

  return assign(value, *value_);

  }

  bool set(T const &value) throw()

  {

  return assign(*value_, value);

  }

private:

  bool assign(T &to, T const &from) throw()

  {

  bool error(false);

  try

  {

  to = from;

  }

  catch (...)

  {

  error = true;

  }

  return error;

  }

  T *value_;

  wrapper(wrapper const &);

  wrapper &operator=(wrapper const &);

  };

 

  (哇!52行,原来只有20行的!而且这还只是一个简单的例子。)

  注意,所有的异常处理函数只是吸收了那些异常而没有做任何处理。虽然这使得wrapper异常安全,却没有纪录下导致这些异常的原因。

  我在Part13中讲的在构造函数上的相冲突的原则在这儿同样适用。异常安全是不够的,并且实际上是达不到预期目的的,如果它掩盖了最初的异常状态的话。同时,如果异常对象在被捕获前就弄死了程序的话,大部分的异常恢复方案都将落空。最后,良好的设计必须满足下两个原则:

l  通过异常对象的存在来注视异常状态,并适当地做出反应。

l  确保创造和传播异常对象不会造成更大的破坏。(别让治疗行为比病本身更糟糕。)

 

1.4  其它说法

   在过去3部分中,我剖析了异常安全。我强烈建议你读一下这些文章:

l  The first principles of C++ exception safety come from Tom Cargill's "Exception Handling: A False Sense of Security," originally published in the November and December 1994 issues of C++ Report. This article, more than any other, alerted us to the true complexities and subtleties of C++ exception handling.

 

 

l  C++ Godfather Bjarne Stroustrup is writing an exception-safety Appendix for his book The C++ Programming Language (Third Edition) (http://www.research.att.com/~bs/3rd.html). Bjarne's offering a draft version (http://www.research.att.com/~bs/3rd_safe0.html) of that chapter on the Inte.NET.

 

 

l  I tend to think of exception safety in terms of contracts and guarantees, ideas formalized in Bertrand Meyer's "Design by Contract" (http://www.eiffel.com/doc/manuals/technology/contract/page.html) programming philosophy. Bertrand realizes this philosophy in both his seminal tome object-Oriented Software Construction (http://www.eiffel.com/doc/oosc.html) and his programming language Eiffel (http://www.eiffel.com/eiffel/page.html).

 

 

l  Herb Sutter has written the most thorough C++ exception-safety treatise I've seen. He's published it as Items 8-19 of his new book Exceptional C++ (http://www1.fatbrain.com/ASP/bookinfo/bookinfo.asp?theisbn=0201615622). If you've done time on Usenet's comp.lang.c++.moderated newsgroup, you've seen Herb's Guru of the Week postings. Those postings inspired the bulk of his book. Highly recommended.

 

 

l  Herb's book features a forward written by SCOtt Meyers. Scott covers exception safety in Items 9-15 of his disturbingly popular collection More Effective C++ (http://www1.fatbrain.com/asp/bookinfo/bookinfo.asp?theisbn=020163371X). If you don't have this book, you simply must acquire it; otherwise Scott's royalties could dry up, and he'd have to get a real job like mine.

 

Scott(在他的Item14)认为,不应该将异常规格申明加到模板成员上,和我的正相反。事实是无论用不用异常规格申明,总有一部分程序需要保护所有异常,以免程序自毁。Scott公正地指出不正确的异常规格申明将导致std::unexpected――这正是他建议你避开的东西;但,在本系列的Part11,我指出unexpected比不可控的异常传播要优越。

  最后要说的是,这儿不会只有一个唯一正确的答案的。我相信异常规格申明可以导致更可预知和有限度的异常行为,即使是对于模板。我也得坦率地承认,在异常/模板混合体上我也没有足够经验,尤其是对大系统。我估计还很少有人有这种经验,因为(就我所知)还没有哪个编译器支持C++标准在异常和模板上的全部规定。


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

请登录后发表评论 登录
全部评论
  • 博文量
    6241
  • 访问量
    2410698