国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

在C++中審慎使用異常規格

2019-11-17 05:46:28
字體:
來源:轉載
供稿:網友

  毫無疑問,異常規格是一個引人注目的特性。它使得代碼更輕易理解,因為它明確地描述了一個函數可以拋出什么樣的異常。但是它不只是一個有趣的注釋。編譯器在編譯時有時能夠檢測到異常規格的不一致。而且假如一個函數拋出一個不在異常規格范圍里的異常,系統在運行時能夠檢測出這個錯誤,然后一個非凡函數uneXPected將被自動地調用。異常規格既可以做為一個指導性文檔同時也是異常使用的強制約束機制,它似乎有著很誘人的外表。

  不過在通常情況下,美貌只是一層皮,外表的漂亮并不代表其內在的素質。函數unexpected缺省的行為是調用函數terminate,而terminate缺省的行為是調用函數abort,所以一個違反異常規格的程序其缺省的行為就是halt(停止運行)。在激活的stack frame中的局部變量沒有被釋放,因為abort在關閉程序時不進行這樣的清除操作。對異常規格的觸犯變成了一場并不應該發生的災難。

  不幸的是,我們很輕易就能夠編寫出導致發生這種災難的函數。編譯器僅僅部分地檢測異常的使用是否與異常規格保持一致。一個函數調用了另一個函數,并且后者可能拋出一個違反前者異常規格的異常,(A函數調用B函數,因為B函數可能拋出一個不在A函數異常規格之內的異常,所以這個函數調用就違反了A函數的異常規格 譯者注)編譯器不對此種情況進行檢測,并且語言標準也禁止它們拒絕這種調用方式(盡管可以顯示警告信息)。

  例如函數f1沒有聲明異常規格,這樣的函數就可以拋出任意種類的異常:

extern void f1(); // 可以拋出任意的異常
  假設有一個函數f2通過它的異常規格來聲明其只能拋出int類型的異常:

void f2() throw(int);
  f2調用f1是非常合法的,即使f1可能拋出一個違反f2異常規格的異常:

void f2() throw(int)
{
 ...
 f1(); // 即使f1可能拋出不是int類型的
 //異常,這也是合法的。
 ...
}
  當帶有異常規格的新代碼與沒有異常規格的老代碼整合在一起工作時,這種靈活性就顯得很重要。

  因為你的編譯器答應你調用一個函數其拋出的異常與發出調用的函數的異常規格不一致,并且這樣的調用可能導致你的程序執行被終止,所以在編寫軟件時采取措施把這種不一致減小到最少。一種好方法是避免在帶有類型參數的模板內使用異常規格。例如下面這種模板,它似乎不能拋出任何異常:

// a poorly designed template wrt exception specifications
template<class T>
bool Operator==(const T& lhs, const T& rhs) throw()
{
 return &lhs == &rhs;
}
  這個模板為所有類型定義了一個操作符函數operator==。對于任意一對類型相同的對象,假如對象有一樣的地址,該函數返回true,否則返回false。

  這個模板包含的異常規格表示模板生成的函數不能拋出異常。但是事實可能不會這樣,因為opertor&(地址操作符)能被一些類型對象重載。假如被重載的話,當調用從operator==函數內部調用opertor&時,opertor&可能會拋出一個異常,這樣就違反了我們的異常規格,使得程序控制跳轉到unexpected。

  上述的例子是一種更一般問題的特例,這個問題也就是沒有辦法知道某種模板類型參數拋出什么樣的異常。我們幾乎不可能為一個模板提供一個有意義的異常規格。,因為模板總是采用不同的方法使用類型參數。解決方法只能是模板和異常規格不要混合使用。

  能夠避免調用unexpected函數的第二個方法是假如在一個函數內調用其它沒有異常規格的函數時應該去除這個函數的異常規格。這很輕易理解,但是實際中輕易被忽略。比如答應用戶注冊一個回調函數:

// 一個window系統回調函數指針
//當一個window系統事件發生時
typedef void (*CallBackPtr)(int eventXLocation,int eventYLocation,void *dataToPassBack);
//window系統類,含有回調函數指針,
//該回調函數能被window系統客戶注冊
class CallBack {
 public:
  CallBack(CallBackPtr fPtr, void *dataToPassBack): func(fPtr), data(dataToPassBack) {}
  void makeCallBack(int eventXLocation,
  int eventYLocation) const throw();
 PRivate:
  CallBackPtr func; // function to call when
  // callback is made
  void *data; // data to pass to callback
}; // function
// 為了實現回調函數,我們調用注冊函數,
//事件的作標與注冊數據做為函數參數。
void CallBack::makeCallBack(int eventXLocation,int eventYLocation) const throw()
{
 func(eventXLocation, eventYLocation, data);
}
  這里在makeCallBack內調用func,要冒違反異常規格的風險,因為無法知道func會拋出什么類型的異常。

  通過在程序在CallBackPtr typedef中采用更嚴格的異常規格來解決問題:


typedef void (*CallBackPtr)(int eventXLocation,int eventYLocation,
void *dataToPassBack) throw();
  這樣定義typedef后,假如注冊一個可能會拋出異常的callback函數將是非法的:

// 一個沒有異常給各的回調函數
void callBackFcn1(int eventXLocation, int eventYLocation,
void *dataToPassBack);
void *callBackData;
...
CallBack c1(callBackFcn1, callBackData);
//錯誤!callBackFcn1可能
// 拋出異常
//帶有異常規格的回調函數
void callBackFcn2(int eventXLocation,
int eventYLocation,
void *dataToPassBack) throw();
CallBack c2(callBackFcn2, callBackData);
// 正確,callBackFcn2
// 沒有異常規格
  傳遞函數指針時進行這種異常規格的檢查,是語言的較新的特性,所以有可能你的編譯器不支持這個特性。假如它們不支持,那就依靠你自己來確保不能犯這種錯誤。

  避免調用unexpected的第三個方法是處理系統本身拋出的異常。這些異常中最常見的是bad_alloc,當內存分配失敗時它被operator new 和operator new[]拋出。假如你在函數里使用new操作符,你必須為函數可能碰到bad_alloc異常作好預備。
  現在常說預防勝于治療(即做任何事都要未雨綢繆 譯者注),但是有時卻是預防困難而治療輕易。也就是說有時直接處理unexpected異常比防止它們被拋出要簡單。例如你正在編寫一個軟件,精確地使用了異常規格,但是你必須從沒有使用異常規格的程序庫中調用函數,要防止拋出unexpected異常是不現實的,因為這需要改變程序庫中的代碼。

  雖然防止拋出unexpected異常是不現實的,但是C++答應你用其它不同的異常類型替換unexpected異常,你能夠利用這個特性。例如你希望所有的unexpected異常都被替換為UnexpectedException對象。你能這樣編寫代碼:

class UnexpectedException {}; // 所有的unexpected異常對象被
//替換為這種類型對象

void convertUnexpected() // 假如一個unexpected異常被
{
 // 拋出,這個函數被調用
 throw UnexpectedException();
}
  通過用convertUnexpected函數替換缺省的unexpected函數,來使上述代碼開始運行。:

set_unexpected(convertUnexpected);
  當你這么做了以后,一個unexpected異常將觸發調用convertUnexpected函數。Unexpected異常被一種UnexpectedException新異常類型替換。假如被違反的異常規格包含UnexpectedException異常,那么異常傳遞將繼續下去,似乎異常規格總是得到滿足。(假如異常規格沒有包含UnexpectedException,terminate將被調用,就似乎你沒有替換unexpected一樣)

  另一種把unexpected異常轉變成知名類型的方法是替換unexpected函數,讓其重新拋出當前異常,這樣異常將被替換為bad_exception。你可以這樣編寫:

void convertUnexpected() // 假如一個unexpected異常被
{
 //拋出,這個函數被調用
 throw; // 它只是重新拋出當前
} // 異常

set_unexpected(convertUnexpected);
// 安裝 convertUnexpected
// 做為unexpected
// 的替代品
  假如這么做,你應該在所有的異常規格里包含bad_exception(或它的基類,標準類exception)。你將不必再擔心假如碰到unexpected異常會導致程序運行終止。任何不聽話的異常都將被替換為bad_exception,這個異常代替原來的異常繼續傳遞。

  到現在你應該理解異常規格能導致大量的麻煩。編譯器僅僅能部分地檢測它們的使用是否一致,在模板中使用它們會有問題,一不注重它們就很輕易被違反,并且在缺省的情況下它們被違反時會導致程序終止運行。異常規格還有一個缺點就是它們能導致unexpected被觸發即使一個high-level調用者預備處理被拋出的異常,比如下面這個幾乎一字不差地來自從條款11例子:

class session {
 // for modeling online
 public: // sessions
  ~Session();
  ...
 
 private:
  static void logDestrUCtion(Session *objAddr) throw();
};

Session::~Session()
{
 try {
  logDestruction(this);
 }
 catch (...) { }
}
  session的析構函數調用logDestruction記錄有關session對象被釋放的信息,它明確地要捕捉從logDestruction拋出的所有異常。但是logDestruction的異常規格表示其不拋出任何異?!,F在假設被logDestruction調用的函數拋出了一個異常,而logDestruction沒有捕捉。我們不會期望發生這樣的事情,凡是正如我們所見,很輕易就會寫出違反異常規格的代碼。當這個異常通過logDestruction傳遞出來,unexpected將被調用,缺省情況下將導致程序終止執行。這是一個正確的行為,這是session析構函數的作者所希望的行為么?作者想處理所有可能的異常,所以似乎不應該不給session析構函數里的catch塊執行的機會就終止程序。假如logDestruction沒有異常規格,這種事情就不會發生。(一種防止的方法是如上所描述的那樣替換unexpected)

  以全面的角度去看待異常規格是非常重要的。它們提供了優秀的文檔來說明一個函數拋出異常的種類,并且在違反它的情況下,會有可怕的結果,程序被立即終止,在缺省時它們會這么做。同時編譯器只會部分地檢測它們的一致性,所以他們很輕易被不經意地違反。而且他們會阻止high-level異常處理器來處理unexpected異常,即使這些異常處理器知道如何去做。綜上所述,異常規格是一個應被審慎使用的公族。在把它們加入到你的函數之前,應考慮它們所帶來的行為是否就是你所希望的行為。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 固原市| 邢台县| 旺苍县| 兴隆县| 岱山县| 乌苏市| 铁力市| 伽师县| 东明县| 徐州市| 洞头县| 广宁县| 宜都市| 新沂市| 大荔县| 高阳县| 天水市| 乌兰浩特市| 安吉县| 和林格尔县| 灵山县| 延边| 万山特区| 浮山县| 新田县| 富蕴县| 虎林市| 四子王旗| 通山县| 湛江市| 翁源县| 耒阳市| 台州市| 平原县| 绿春县| 报价| 东山县| 华池县| 辽阳县| 云南省| 福贡县|