C++ Gotchas 條款64:丟擲String Literals (轉)

worldblog發表於2007-12-14
C++ Gotchas 條款64:丟擲String Literals (轉)[@more@]

Gotcha #64: Throwing String Literals:namespace prefix = o ns = "urn:schemas--com::office" />

條款64:丟擲String Literals

 

許多C++教本的作者在展示異常機制時都丟擲字元文字串(character string literals)資訊:

 

throw "Stack underflow!";

 

他們知道這種實作手法本應迴避,但是他們還是這樣做了,因為那只是“教學性示例”。不幸的是,這樣做示例就隱含了“模仿這個示例”的建議(譯註:畢竟,讀者當然會傾向於效仿所看教本中的示例來學習),而這些作者們通常忽視了還要提示讀者:“真正照這樣做的話,會招致刻意傷害和厄運”。

 

絕不要丟擲string literals作為exception s(異常)。從原理上講,原因是:這些exception objects最終應該被捕獲,而且應該是根據其型別(type)來捕獲,而不是根據其值(value)來捕獲:

 

try {

  // . . .

}

catch( const char *msg ) {

  string m( msg );

  if( m == "stack underflow" ) // . . .

  else if( m == "connection timeout" ) // . . .

  else if( m == "security violation" ) // . . .

  else throw;

}

 

丟擲和捕獲string literals所產生的實際影響是,經由exception object的型別,幾乎沒有包含任何有關異常的資訊。這種不甚嚴密的做法要求“catch clause攔截每一個同類異常並透過檢視其值(value)來判斷是否合乎捕獲條件”。更糟的是,對值的比較也是很不嚴密的;一旦該“錯誤訊息”在大小寫或格式方面有改變,這種比較就毫無效用了。如果在上面的例子中發生這種情況,我們就永遠無法發覺“棧發生下溢(stack underflow)”的情況。

 

同樣的情況也存在於其它預定義型別和標準型別的異常中。丟擲integers,floating point numbers,strings,或者(在一個糟糕透頂的日子)float vector組成的sets,都會引發類似的問題。簡單地說,“丟擲預定義型別之異常物件”的問題在於:當我們捕獲到一個此種異常時,我們無法知道它代表什麼意思,從而也無法確定如何處理異常。丟擲該異常的人好像在愚弄我們:“發生了非常非常糟糕的事情!你猜是什麼?”而我們別無選擇,只好玩起做作的猜謎遊戲,並很可能會玩輸。

 

所謂exception type(異常型別)是一個用來代表異常的抽象資料型別(abstract data type)。設計這些異常型別時所要遵循的原則無異於設計其它任何抽象資料型別:辨識並具名一個概念,為其定出抽象的操作集合並實現之。在實現過程中,要考慮初始化、複製以及型別轉換等問題。很簡單嘛。用string literal來表示一個異常,較之用complex number來表示一個異常,同樣都是沒有多大意義。從理論上講這樣做或許會湊效,但從實際上講這會變得冗長乏味,臭蟲百出。

 

當我們丟擲“代表stack underflow()的異常”時,我們試圖表達什麼樣的抽象概念呢?噢。可不就是它嗎。

 

class StackUnderflow {};

 

通常,一個exception object的型別要能傳達所有關於該異常的資訊;而對於exception types來說,“經由顯式宣告的成員來進行資訊發放(dispense)”也沒什麼不尋常的。然而,“提供描述性文字的能力”經常是信手拈來的。其它不太常需要的相關資訊也可以記錄到exception object當中:

 

class StackUnderflow {

public:

  StackUnderflow( const char *msg = "stack underflow" );

  virtual ~StackUnderflow();

  virtual const char *what() const;

  // . . .

};

 

如果提供了能返回描述性文字的函式,那麼其應該是一個名為what的virtual member function,具有上述的表達形式。這樣做是考慮到該異常與標準異常型別的正交性,因為標準異常型別都提供了這個函式。事實上,讓自定義的異常型別派生自標準異常型別,通常是個好主意:

 

class StackUnderflow : public std::runtime_error {

public:

  explicit StackUnderflow( const char *msg = "stack underflow" )

  : std::runtime_error( msg ) {}

};

 

這就使我們可以將其作為StackUnderflow、較為一般化的runtime_error,或更具一般性的標準異常(runtime_error的public base class)來捕獲。提供一個更一般化但非標準的exception type,通常也是個好主意。一般來說,這種型別被作為base class使用,特定模組或庫可能丟擲的所有exception types都派生自它:

 

class ContainerFault {

public:

  virtual ~ContainerFault();

  virtual const char *what() const = 0;

  // . . .

};

class StackUnderflow

: public std::runtime_error, public ContainerFault {

public:

  explicit StackUnderflow( const char *msg = "stack underflow" )

  : std::runtime_error( msg ) {}

  const char *what() const

  { return std::runtime_error::what(); }

};

 

最後要說的是,為exception types提供尋常的copy和destruction語義也是必需的。特別是,丟擲一個異常就暗示著“copy construct該exception type物件必須是合法的”,因為這正是執行期異常機制在異常被丟擲時要做的事情(詳見Gotcha條款65),而當該異常被處理之後也必須銷燬這個複製。通常可以讓為我們自動編寫這些操作(詳見Gotcha條款49):

 

class StackUnderflow

: public std::runtime_error, public ContainerFault {

public:

  explicit StackUnderflow( const char *msg = "stack underflow" )

  : std::runtime_error( msg ) {}

  // StackUnderflow( const StackUnderflow & );

  // StackUnderflow &operator =( const StackUnderflow & );

  const char *what() const

  { return std::runtime_error::what(); }

};

 

現在,stack type的可以自行選擇,讓stack underflow作為不同層級的異常型別被捕獲,其可以是StackUnderflow(使用者知道自己在使用stack type,希望特別關注它)、較一般化的ContainerFault(使用者知道自己在使用container library,希望捕獲任何container 引發的錯誤)、runtime_error(使用者不知道自己是否在使用哪個container library,希望處理任何標準執行期錯誤),或最一般化的exception(使用者準備處理任何標準異常)。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-993427/,如需轉載,請註明出處,否則將追究法律責任。

相關文章