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

worldblog發表於2007-12-12
C與C++中的異常處理10 (轉)[@more@]

中產生的異常:namespace prefix = o ns = "urn:schemas--com::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++編譯,自鳴得意地坐下來,看著的提示輸出。

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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章