C与C++中的异常处理15

类别:VC语言 点击:0 评论:0 推荐:
1.     模板安全(续)

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

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 <typename T>

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 Internet.

 

 

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++标准在异常和模板上的全部规定。

本文地址:http://com.8s8s.com/it/it2717.htm