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

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

More effective C++:審慎使用異常規(guī)格

2019-11-17 05:46:44
字體:
供稿:網(wǎng)友

  毫無疑問,異常規(guī)格是一個引人注目的特性。它使得代碼更輕易理解,因為它明確地描述了一個函數(shù)可以拋出什么樣的異常。但是它不只是一個有趣的注釋。編譯器在編譯時有時能夠檢測到異常規(guī)格的不一致。而且假如一個函數(shù)拋出一個不在異常規(guī)格范圍里的異常,系統(tǒng)在運行時能夠檢測出這個錯誤,然后一個非凡函數(shù)uneXPected將被自動地調(diào)用。異常規(guī)格既可以做為一個指導(dǎo)性文檔同時也是異常使用的強(qiáng)制約束機(jī)制,它似乎有著很誘人的外表。
 
  不過在通常情況下,美貌只是一層皮,外表的漂亮并不代表其內(nèi)在的素質(zhì)。函數(shù)unexpected缺省的行為是調(diào)用函數(shù)terminate,而terminate缺省的行為是調(diào)用函數(shù)abort,所以一個違反異常規(guī)格的程序其缺省的行為就是halt(停止運行)。在激活的stack frame中的局部變量沒有被釋放,因為abort在關(guān)閉程序時不進(jìn)行這樣的清除操作。對異常規(guī)格的觸犯變成了一場并不應(yīng)該發(fā)生的災(zāi)難。

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

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

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

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

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

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

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

  這個模板包含的異常規(guī)格表示模板生成的函數(shù)不能拋出異常。但是事實可能不會這樣,因為opertor&能被一些類型對象重載。假如被重載的話,當(dāng)調(diào)用從operator==函數(shù)內(nèi)部調(diào)用opertor&時,opertor&可能會拋出一個異常,這樣就違反了我們的異常規(guī)格,使得程序控制跳轉(zhuǎn)到unexpected。

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

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

// 一個window系統(tǒng)回調(diào)函數(shù)指針
//當(dāng)一個window系統(tǒng)事件發(fā)生時
typedef void (*CallBackPtr)(int eventXLocation,
int eventYLocation,
void *dataToPassBack);
//window系統(tǒng)類,含有回調(diào)函數(shù)指針,
//該回調(diào)函數(shù)能被window系統(tǒng)客戶注冊
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
// 為了實現(xiàn)回調(diào)函數(shù),我們調(diào)用注冊函數(shù),
//事件的作標(biāo)與注冊數(shù)據(jù)做為函數(shù)參數(shù)。
void CallBack::makeCallBack(int eventXLocation,
int eventYLocation) const throw()
{
 func(eventXLocation, eventYLocation, data);
}
  這里在makeCallBack內(nèi)調(diào)用func,要冒違反異常規(guī)格的風(fēng)險,因為無法知道func會拋出什么類型的異常。

  通過在程序在CallBackPtr typedef中采用更嚴(yán)格的異常規(guī)格來解決問題:


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

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

  避免調(diào)用unexpected的第三個方法是處理系統(tǒng)本身拋出的異常。這些異常中最常見的是bad_alloc,當(dāng)內(nèi)存分配失敗時它被operator new 和operator new[]拋出(參見條款8)。假如你在函數(shù)里使用new操作符(還參見條款8),你必須為函數(shù)可能碰到bad_alloc異常作好預(yù)備。

  現(xiàn)在常說預(yù)防勝于治療(即做任何事都要未雨綢繆 譯者注),但是有時卻是預(yù)防困難而治療輕易。也就是說有時直接處理unexpected異常比防止它們被拋出要簡單。例如你正在編寫一個軟件,精確地使用了異常規(guī)格,但是你必須從沒有使用異常規(guī)格的程序庫中調(diào)用函數(shù),要防止拋出unexpected異常是不現(xiàn)實的,因為這需要改變程序庫中的代碼。

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

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

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

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

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

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

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

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

class session { // for modeling online
public: // sessions
~Session();
...

private:
static void logDestrUCtion(Session *objAddr) throw();
};

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

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


發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 裕民县| 南丰县| 宁南县| 内江市| 田阳县| 乌鲁木齐市| 堆龙德庆县| 富蕴县| 衡南县| 旌德县| 庆城县| 临安市| 全州县| 巧家县| 太和县| 花垣县| 尖扎县| 屯留县| 任丘市| 南召县| 汉中市| 南和县| 武安市| 东丽区| 罗平县| 淮阳县| 霍林郭勒市| 新巴尔虎左旗| 朝阳县| 仁布县| 枞阳县| 屯门区| 岳阳县| 县级市| 新兴县| 噶尔县| 公安县| 渑池县| 积石山| 乌鲁木齐市| 松原市|