C與C++中的異常處理15 (轉)

gugu99發表於2008-08-06
C與C++中的異常處理15 (轉)[@more@]

(續):namespace prefix = o ns = "urn:schemas--com::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 detenism 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++ Godher Bjarne Stroustrup is writing an exception-safety Appendix for his book The C++ Programming Language (Third Edition) (~bs/3rd.html). Bjarne's offering a draft version (~bs/3rd_safe0.html) of that chapter on the Inte.

 

 

l  I tend to think of exception safety in terms of contracts and guarantees, as formalized in Bertrand Meyer's "Design by Contract" () programming philosophy. Bertrand realizes this philosophy in both his seminal tome -Oriented Software Construction () and his programming language Eiffel ().

 

 

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++ (/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 tt Meyers. Scott covers exception safety in Items 9-15 of his disturbingly popular collection More Effective C++ (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/10748419/viewspace-1008588/,如需轉載,請註明出處,否則將追究法律責任。

相關文章