性的最有效方法之一C語言實(shí)現(xiàn)出錯處理的方法是.ppt_第1頁
性的最有效方法之一C語言實(shí)現(xiàn)出錯處理的方法是.ppt_第2頁
性的最有效方法之一C語言實(shí)現(xiàn)出錯處理的方法是.ppt_第3頁
性的最有效方法之一C語言實(shí)現(xiàn)出錯處理的方法是.ppt_第4頁
性的最有效方法之一C語言實(shí)現(xiàn)出錯處理的方法是.ppt_第5頁
已閱讀5頁,還剩72頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

第九章 異常處理,程序的錯誤有兩大類: 編譯鏈接錯誤:這類錯誤是由程序的語法錯誤(例 如關(guān)鍵字錯誤、變量未定義、語句結(jié)束缺分號、括 號失配、結(jié)構(gòu)失配等)和其他錯誤(函數(shù)只聲明未 定義、缺少庫的鏈接配置等)引起的。這類程序錯 誤發(fā)生在程序的編譯鏈接過程中,對于一個具有一 定經(jīng)驗(yàn)的編程人員是容易解決的。 運(yùn)行錯誤:這類程序錯誤發(fā)生在程序的運(yùn)行期間, 主要表現(xiàn)在計(jì)算過程中的被0除、內(nèi)存空間不足、數(shù) 據(jù)的輸入輸出錯誤等。這類程序錯誤只靠編程人員 的經(jīng)驗(yàn)是難以避免的。,錯誤修復(fù)技術(shù)是解決程序運(yùn)行錯誤,提高代碼健壯 性的最有效方法之一。C 語言實(shí)現(xiàn)出錯處理的方法是 出錯與錯誤處理的緊耦合,即檢查被調(diào)函數(shù)的返回值 或輸出信息,以便確定是否發(fā)生錯誤,作出相應(yīng)的處 理。這種出錯處理存在兩個主要問題: 出錯處理的繁瑣和錯誤檢查引起的代碼膨脹將不可避 免地降低程序的執(zhí)行效率,增加程序的閱讀困難。 被調(diào)用函數(shù)只清楚出錯原因而不清楚被調(diào)用環(huán)境,因 此缺乏處理錯誤的依據(jù)。因此這種將用戶函數(shù)與出 錯處理緊密結(jié)合的方法將造成使用出錯處理的不方 便和難以接受。,正是因?yàn)樯鲜鲈?,使得不少程序設(shè)計(jì)人員在實(shí)際 設(shè)計(jì)中常常 “忽略” 出錯處理,似乎是在 “不會出錯” 的 狀態(tài)下編程,這會嚴(yán)重地降低程序代碼的健壯性。 異常處理是 C+ 語言的一個重要特征,它提出了出 錯處理更加完美的方法。 出錯處理代碼的編寫不再繁瑣,也不須將出錯處理代 碼與功能代碼緊密結(jié)合。在可能發(fā)生錯誤的函數(shù)中 加入出錯代碼,并在后面調(diào)用該函數(shù)的程序中加入 錯誤處理代碼。如果程序中多次調(diào)用一個函數(shù),可以在程序中加入一個專門用于被調(diào)函數(shù)的出錯處理 函數(shù)。, 錯誤發(fā)生是不會被忽略的。如果被調(diào)用函數(shù)需發(fā)送 一條出錯信息給調(diào)用函數(shù),它可向調(diào)用環(huán)境發(fā)送一 個描述錯誤信息的對象。如果調(diào)用環(huán)境沒有捕獲該 錯誤信息對象,則該錯誤信息對象會被自動向上一 層的調(diào)用環(huán)境發(fā)送;如果調(diào)用環(huán)境無法處理該錯誤 信息對象,則調(diào)用環(huán)境可以將該錯誤信息對象主動 發(fā)送到上一層的調(diào)用環(huán)境中;直到該錯誤信息對象 被捕捉和處理。,9.1 C 語言的出錯處理 在通過對被調(diào)用函數(shù)的返回或?qū)嘌院?assert() 的判 斷結(jié)果的檢查能夠確切定后續(xù)操作的情況下,出錯處 理就變得十分明確和容易了,因?yàn)榭梢酝ㄟ^程序執(zhí)行 的當(dāng)前運(yùn)行環(huán)境得到所有必要的信息。然而能夠這樣 處理的錯誤都是環(huán)境一般都是簡單的普通錯誤。 如果錯誤問題發(fā)生時,在程序當(dāng)前運(yùn)行環(huán)境中無法 獲得足夠的錯誤發(fā)生和處理的信息,則需要從更大的 運(yùn)行環(huán)境中獲取出錯處理信息。C 語言處理這類錯誤 情況的典型方法有三種:, 出錯信息可以通過函數(shù)的返回值獲得。如果返回值 不足以描述出錯信息,則可設(shè)置全局錯誤判斷標(biāo)志 (標(biāo)準(zhǔn) C 語言中全局變量 errno 以及系統(tǒng)運(yùn)行庫函 數(shù) perror(const char *string) 、strerror(int errnum) 支持這 一方法)。由于這種方法要對每個函數(shù)調(diào)用都進(jìn)行錯 誤檢查,這將十分繁瑣并增加程序的混亂度。另 外,偶然出現(xiàn)異常的函數(shù)返回值可能并不反映什麼 問題。, 可使用 C 信號處理庫中的 signal 函數(shù)設(shè)置中斷信號 處理,和使用 raise 函數(shù)向正在運(yùn)行的程序發(fā)送信 號。這兩個函數(shù)的原型如下: void(*signal(int sig, void(_cdecl *func)(int sig, int subcode) ) (int sig); int raise( int sig ); 信號處理庫的使用者必須清楚地了解、恰當(dāng)?shù)囟x 和設(shè)置中斷信號處理。同時對于大型項(xiàng)目,不同庫 之間的信號可能會產(chǎn)生沖突。因此,信號處理庫的 使用有一定的難度。, 使用 C 標(biāo)準(zhǔn)庫中的設(shè)置跳轉(zhuǎn)函數(shù) setjmp 和非局部跳 轉(zhuǎn)函數(shù) longjmp 實(shí)現(xiàn)出錯和錯誤處理。這兩個函數(shù) 的原型如下: setjmp(jmp_buf env); longjmp(jmp_buf env, int value); 調(diào)用 setjmp 函數(shù)在程序中存儲一典型的正常狀態(tài), 如果進(jìn)入錯誤狀態(tài),longjmp 可恢復(fù)由 setjmp 函數(shù)所 設(shè)定的狀態(tài),并且狀態(tài)被恢復(fù)時的存儲地點(diǎn)與錯誤 發(fā)生地點(diǎn)緊密聯(lián)系。,在較早的 Visual C+ 版本(例如 VC+ 6.0)中 C 語 言的信號處理技術(shù)和 setjmp/longjmp 函數(shù)被調(diào)用時不能 正確地調(diào)用類對象的析構(gòu)函數(shù),所以一個描述出錯信 息的對象不能被正確地清除。如果出錯對象不能被清 除,則該對象將被保留下來且不能再次被正確地存 取,因此,實(shí)際上是不可能有效、正確地從異常情況 中恢復(fù)。所以在 C+ 編程中不推薦使用 C 語言中這種 處理出錯的方法。 例9-1 描述了 setjmp/longjmp 函數(shù)的這一特點(diǎn)。,在 VC+ 6.0 中的運(yùn)行結(jié)果: tornado, witch, munchkins. theres no place like home theres no place like home theres no place like home rainbow() is called. Auntie Em! I had the strangest dream. 分析: 由于 main 函數(shù)中 setjmp 的調(diào)用(保存當(dāng)前運(yùn)行點(diǎn)環(huán) 境)后返回 0,導(dǎo)致 if 分支的執(zhí)行,顯示了前 5 行 執(zhí)行信息。, 全局函數(shù) OZ 被調(diào)用,其中 longjmp 的調(diào)用結(jié)果使得 程序的運(yùn)行恢復(fù)到由 setjmp 保存的運(yùn)行點(diǎn),導(dǎo)致使 setjmp 再次被調(diào)用。由于保存運(yùn)行點(diǎn)環(huán)境的緩沖區(qū) kansas 被 setjmp 的第一次調(diào)用設(shè)置,使得 setjmp 的 第二次調(diào)用返回值為非 0,導(dǎo)致 else 分支執(zhí)行,顯 示第 6 行運(yùn)行信息,但 rainbow 對象 RB 未被析構(gòu)。 結(jié)論: 程序中調(diào)用 setjmp 和 longjmp 的方法提供了修復(fù)程 序運(yùn)行錯誤的典型結(jié)構(gòu)和方法。 使用 setjmp 和 longjmp 可能無法析構(gòu)錯誤發(fā)生前創(chuàng) 建的對象,不能滿足面向?qū)ο蟪绦虻腻e誤處理。,9.2 C+ 語言的出錯處理 雖然 C 語言的出錯處理技術(shù)和方法在 C+ 語言的程 序設(shè)計(jì)中仍然可以使用,但更重要的是 C+ 提供了一 套符合面向?qū)ο蟪绦蛟O(shè)計(jì)的出錯處理技術(shù) 異常處 理技術(shù)。異常處理的特點(diǎn)主要表現(xiàn)在: 異常的產(chǎn)生和異常的處理分離。 異常的信息數(shù)據(jù)和行為被封裝成獨(dú)立的類對象。 結(jié)構(gòu)化的異常處理為異常對象的捕獲、處理、傳遞 提供了有效、方便的編程手段。 能確保殘留對象在異常處理時被徹底析構(gòu)。,9.3 異常對象的創(chuàng)建和拋出 如果程序發(fā)生了異常情況,而在當(dāng)前運(yùn)行環(huán)境中無 法獲取處理異常的足夠信息,就可以創(chuàng)建一個包含異 常信息和行為的對象,并將該對象從發(fā)生異常的運(yùn)行 環(huán)境中拋出,發(fā)送到上一層運(yùn)行環(huán)境中,這稱為異常 的創(chuàng)建和拋出,例如: throw myerror(“something bad happened“); 分析: 關(guān)鍵字 throw 的作用是拋出異常對象,被拋出的對象可以是包括系統(tǒng)預(yù)定義類型在內(nèi)的任何類型,當(dāng)然多數(shù)情況為自定義異常類型,如本例中的 myerror 就是一個以字符串為參數(shù)創(chuàng)建的自定義異常對象。, 在函數(shù)中使用關(guān)鍵字 throw 拋出異常類對象都是函 數(shù)正常執(zhí)行中不存在的。拋出異常類對象的結(jié)果是 導(dǎo)致函數(shù)退出執(zhí)行,但不是函數(shù)設(shè)計(jì)的正常返回。 因此,異常類型可以視為是函數(shù)異常退出作用域的 返回值類型。 函數(shù)的正常返回和對函數(shù)返回的檢查和處理是處于 同一作用域的,而對異常的處理作用域與異常拋出 作用域可能相距很遠(yuǎn)。注意,只有完整創(chuàng)建的異常 對象才能在異常被處理時被清除,而函數(shù)返回(包 括異常返回)時,函數(shù)作用域內(nèi)的所有對象均被清 除。, 函數(shù)可以根據(jù)錯誤發(fā)生的原因不同,拋出不同類型 的異常對象,使函數(shù)的調(diào)用者可以在更大范圍的程 序上下文環(huán)境中考慮對異常的處理。,9.4 異常的捕獲和處理 函數(shù)在發(fā)生錯誤時能以拋出異常對象的方式結(jié)束函 數(shù)執(zhí)行是建立在假定該異常對象能被捕獲和處理的前 提下的。這一假定在 C+ 中是成立的,這也是異常處 理的一個優(yōu)點(diǎn)。完成函數(shù)調(diào)用時的異常測試,異常對 象的捕獲和處理是由 try - catch 結(jié)構(gòu)實(shí)現(xiàn)的,使得處理 程序運(yùn)行錯誤的編碼變得方便、有效,并具有完全的 結(jié)構(gòu)化和良好的可讀性。該結(jié)構(gòu)的一般形式如下: try 被測試的程序代碼 catch(異常類型 異常對象名) 異常處理的程序代碼 ,9.4.1 測試塊 try 測試塊 try 的作用是使處于該塊中的程序代碼執(zhí)行可 能拋出的異常對象能在后續(xù)的異常處理器中被捕獲, 從而確定如何處理。因此,調(diào)用一個函數(shù),并期望在 函數(shù)調(diào)用者所在程序運(yùn)行環(huán)境中使用異常處理的方法 解決函數(shù)可能發(fā)生的錯誤,就必須將函數(shù)調(diào)用語句置 于測試塊 try 中。否則函數(shù)所拋出的異常對象就不能被 后續(xù)的異常處理器捕獲,從而使異常對象被自動傳遞 到上一層運(yùn)行環(huán)境,直至被操作系統(tǒng)捕獲和處理,導(dǎo) 致程序被終止執(zhí)行。,9.4.2 異常處理器 異常發(fā)生后,被拋出的異常對象一旦被隨后的異常 處理器捕獲到,就可以被處理。根據(jù)在當(dāng)前運(yùn)行環(huán)境 中能否解決引起異常的程序運(yùn)行錯誤,對異常對象的 處理有兩種: 嘗試解決程序運(yùn)行錯誤,析構(gòu)異常對象。 無法解決程序運(yùn)行錯誤,將異常對象拋向上一層運(yùn) 行環(huán)境。 為此,異常處理器應(yīng)該具備捕獲一個以上任何類型 的異常對象的能力,每個異常對象的捕獲和處理由關(guān) 鍵字 catch 引導(dǎo)。 例如:,try / code that may generate exception catch(type1 id1) / handle exceptions of type1 catch(type2 id2) / handle exceptions of type2 / etc, 每個 catch 語句相當(dāng)于一個以特定的異常類型為單 一參數(shù)的小型函數(shù); 標(biāo)識符 id1、id2 等如同函數(shù)中的參數(shù)名,如果對引 起該異常對象拋出的程序運(yùn)行的錯誤處理中無須使 用異常對象,則該標(biāo)識符可省略; 異常處理器部分必須緊跟在測試塊 try 之后; catch 語句與 switch 語句不同,即每個 case(情況) 引起的執(zhí)行需要加入 break 實(shí)現(xiàn)執(zhí)行的結(jié)束; 測試塊 try 中不同函數(shù)的調(diào)用可能會拋出相同的異常 對象,而異常處理器中對同一異常對象的處理方法 只需要一個。,異常處理的兩種模式 終止與恢復(fù): 終止模式 如果引起異常的是致命錯誤,即表明程序運(yùn)行進(jìn)入 了無法恢復(fù)正常運(yùn)行的狀態(tài),這時必須調(diào)用終止模 式結(jié)束程序運(yùn)行的異常狀態(tài),而不應(yīng)返回異常拋出 之處。 恢復(fù)模式 恢復(fù)意味著期望對異常的處理能夠修復(fù)異常狀態(tài), 然后再次對拋出異常對象的函數(shù)進(jìn)行測試調(diào)用,使 之能夠成功運(yùn)行。,如果希望程序具有恢復(fù)運(yùn)行的能力,就需要程序在 異常處理后仍能繼續(xù)正常執(zhí)行,這時異常處理就更 像一個被調(diào)用的函數(shù)。在程序需要進(jìn)行恢復(fù)運(yùn)行的 地方,可以將測試塊 try 和異常處理器放在 while 循 環(huán)中,直到測試調(diào)用得到滿意的結(jié)果。,9.4.3 異常規(guī)格說明 編寫異常處理器必須知道被測試調(diào)用的函數(shù)能拋出 哪些類型的異常對象。C+ 提供了異常規(guī)格說明語 法,即在函數(shù)原型聲明中,位于參數(shù)表列之后,清晰 地告訴函數(shù)的使用者:該函數(shù)可能拋出的異常類型, 以便使用者能夠方便地捕獲異常對象進(jìn)行異常處理。 帶有異常規(guī)格說明的函數(shù)原型說明的一般形式: 返回類型 函數(shù)名(參數(shù)表列) throw (異常類型名, ),使用異常規(guī)格說明的函數(shù)原型有三種: 拋出指定類型異常對象的函數(shù)原型: void function() throw(toobig, toosmall, divzero); 能拋出任何類型異常對象的函數(shù)原型: void function(); 注意,該形式與傳統(tǒng)的函數(shù)原型聲明形式相同。 不拋出任何異常對象的函數(shù)原型: void function() throw(); 為了實(shí)現(xiàn)對函數(shù)的安全調(diào)用和對函數(shù)執(zhí)行中可能產(chǎn) 生的錯誤進(jìn)行有效的處理。應(yīng)該在編寫每個有可能拋 出異常的函數(shù)時都應(yīng)當(dāng)加入異常規(guī)格說明。,需要特別注意的是:如果函數(shù)的執(zhí)行錯誤所拋出的 異常對象類型并未在函數(shù)的異常規(guī)格說明中聲明,則 會導(dǎo)致系統(tǒng)函數(shù) unexpected() 被調(diào)用,以便解決未預(yù)見 錯誤引起的異常。 unexpected() 是由函數(shù)指針實(shí)現(xiàn)函數(shù)調(diào)用的,因此我 們可通過改變函數(shù)指針?biāo)赶虻暮瘮?shù)執(zhí)行代碼的入口 地址來改變相對應(yīng)的處理操作。這就意味著用戶定義 自己特定的對未預(yù)見錯誤的處理方法(系統(tǒng)的缺省處 理操作將最終導(dǎo)致程序終止運(yùn)行)。實(shí)現(xiàn)自定義處理 方法設(shè)定是調(diào)用系統(tǒng)函數(shù) set_unexpected() 完成的。,該函數(shù)的原型如下: typedef void (*unexpected_function)(); unexpected_function set_unexpected( unexpected_function unexp_func ); 該函數(shù)可以將一個自定義的處理函數(shù)地址 unexp_func 設(shè)置為 unexpected 的函數(shù)指針新值,并返回該指針的 當(dāng)前值,以便保存,并用于恢復(fù)原處理方法。,例9-2 展示了如何進(jìn)行函數(shù)的異常規(guī)格說明和使用系 統(tǒng)函數(shù) set_unexpected() 處理未預(yù)見錯誤。 程序分析: 異常類型通常是小規(guī)模的(如本例的 up、fit),但 有時也需要異常類型包含大量的錯誤信息,使得異 常處理器可以依據(jù)這些信息確定恰當(dāng)?shù)慕鉀Q方法。 函數(shù) g() 有兩個定義版本: 版本1不拋出異常,使得在函數(shù) f() 中調(diào)用 g() 時 無異常拋出,這與 f() 的異常規(guī)格說明一致。 版本2有異常拋出,使得在函數(shù) f() 中調(diào)用 g() 時 有異常拋出,違反了 f() 的異常規(guī)格說明。, 處理未預(yù)見異常的自定義函數(shù) my_unexpected() 沒有 返回值,但卻可以拋出一個新異常(也可以拋出被 處理的未預(yù)見異常)或者直接調(diào)用系統(tǒng)函數(shù) exit() 或 abort()。 使用 set_unexpected() 將 my_unexpected() 設(shè)置為系 統(tǒng)函數(shù) unexpected 的函數(shù)指針新值,并將返回的指 針當(dāng)前值保存在一個類型為 unexpected_function 的變 量中,以便恢復(fù) unexpected 的函數(shù)指針的原值。 為了使程序能對所有的潛在異常進(jìn)行檢測,將測試 塊 try 放入 for 循環(huán)中,這與前面講到的使用異常處 理器的恢復(fù)模式很相似。,9.4.4 捕獲所有的異常 如果函數(shù)定義時沒有異常規(guī)格說明,則在該函數(shù)被 調(diào)用時就有可能拋出任何類型的異常對象。為了解決 這個問題,應(yīng)該在異常處理器中增加一個能捕獲任意 類型的異常對象的處理分支。例如: catch() cout “an unkown exception was thrown endl; ,注意: 應(yīng)將能捕獲任意異常的處理分支放在異常處理器的 最后,避免遺漏對可預(yù)見異常的處理。 使用省略號 “” 做為 catch 的參數(shù)可以捕獲所有異 常,但無法知道所捕獲異常的類型。另外省略號不 能與其他異常類型同時作為 catch 的參數(shù)使用。,9.4.5 異常的重新拋出 如果運(yùn)行錯誤拋出的異常對象雖然被直接調(diào)用環(huán)境 中的異常處理器捕獲,但根據(jù)所獲得錯誤信息在當(dāng)前 運(yùn)行環(huán)境中無法對異常進(jìn)行恰當(dāng)?shù)奶幚?,則需要將所 捕獲的異常對象從當(dāng)前的運(yùn)行環(huán)境重新拋向高一層運(yùn) 行環(huán)境,使產(chǎn)生異常的錯誤能在高一層運(yùn)行環(huán)境中得 到恰當(dāng)?shù)奶幚砘驅(qū)惓ο罄^續(xù)重新拋出。注意,如 果在當(dāng)前運(yùn)行環(huán)境中沒有捕獲運(yùn)行錯誤拋出的異常對 象,則重新拋出異常對象的操作是自動發(fā)生的。,實(shí)現(xiàn)重新拋出異常的方法是在捕獲異常對象的異常 處理器分支中使用不帶參數(shù)的 throw 語句。例如使用省 略號做參數(shù)捕獲任意類型異常對象時,由于無法得到 有關(guān)異常的信息而將異常對象重新拋出: catch() cout “an unkown exception was thrown“ endl; throw; 由于每個被拋出異常對象在未被處理之前是被保留 的,所以更高層次的運(yùn)行環(huán)境的處理器總可以獲得來 自這個異常對象的完整信息。,9.4.6 未捕獲的異常 如果測試塊 try 執(zhí)行過程中拋出的異常對象在當(dāng)前的 異常處理器沒有被捕獲,則異常對象將進(jìn)入更高一層 的運(yùn)行環(huán)境中。這種異常對象的拋出、捕獲、處理過 程按照運(yùn)行環(huán)境的調(diào)用關(guān)系逐層進(jìn)行,直到在某個層 次的運(yùn)行環(huán)境的異常處理器中捕獲并恰當(dāng)處理了異常 對象才停止,否則將一直進(jìn)行到調(diào)用系統(tǒng)的特定函數(shù) terminate() 終止程序的運(yùn)行。例如,在異常對象的創(chuàng)建 過程中、異常對象的被處理過程中或異常對象的析構(gòu) 過程中又拋出了新異常對象,就會產(chǎn)生所拋出的異常 對象不能被捕獲。,注意: 特殊函數(shù) terminate() 也是一個使用函數(shù)指針實(shí)現(xiàn)調(diào) 用的函數(shù),因此允許用戶定義自己特定的程序終止 函數(shù)。在 C 標(biāo)準(zhǔn)庫中,terminate() 的函數(shù)指針的缺 省值是指向系統(tǒng)函數(shù) abort() 的調(diào)用地址。abort() 的 功能是不調(diào)用正常的程序終止函數(shù)而直接從程序中 退出。 使用系統(tǒng)函數(shù) set_terminate 來設(shè)定用戶自定義的終 止函數(shù)作為 terminate() 函數(shù)的新執(zhí)行函數(shù),取代該 函數(shù)的當(dāng)前執(zhí)行函數(shù),并可以通過 set_terminate 的 返回值獲得 terminate() 的當(dāng)前執(zhí)行函數(shù)調(diào)用地址。, 用戶的自定義終止函數(shù)除了必須不含輸入?yún)?shù),其 返回值必須為 void 外,不能拋出任何異常對象,但 可以調(diào)用一些程序終止函數(shù),如 abort() 。 在實(shí)際應(yīng)用中,如果函數(shù) terminate() 被調(diào)用,就意味 著程序的運(yùn)行錯誤已經(jīng)無法被恢復(fù)。 例9-3 展示了異常對象的析構(gòu)過程中拋出異常對象將 導(dǎo)致程序終止函數(shù) terminate() 被調(diào)用。為了能觀 察到 terminate() 被調(diào)用的情況,定義了一個自定 義的終止函數(shù),并使用 set_terminate 將其設(shè)定為 terminate() 的新執(zhí)行函數(shù)。,9.5 異常處理中對象的清除 異常處理是否有效的關(guān)鍵就在于:當(dāng)異常對象被拋 出,程序從正常流程轉(zhuǎn)入異常處理器的執(zhí)行流程時, 正常流程執(zhí)行環(huán)境中所創(chuàng)建的對象除異常對象外必須 被正確地清除。 C+ 的異常處理器可以保證程序流程離開一個運(yùn)行 環(huán)境的作用域時,該作用域中所有結(jié)構(gòu)完整的對象的 析構(gòu)函數(shù)將被調(diào)用,以確保清除這些對象。因此,如 何保證對象的完整創(chuàng)建就成為需要特別關(guān)注的問題。,例9-4 描述了由于在對象的創(chuàng)建過程中發(fā)生異常,使對 象不能被完整創(chuàng)建,導(dǎo)致異常對象拋出時這些不 完整的對象不能被清除。同時還展示了如果 unexpected() 函數(shù)執(zhí)行中再次拋出意外的異常對 象時將會產(chǎn)生什麼結(jié)果。 程序運(yùn)行結(jié)果被記錄在文本文件 cleanup.out 中,其內(nèi) 容如下: constructing noisy 0 name before array noisy:new constructing noisy 1 name array elem constructing noisy 2 name array elem constructing noisy 3 name array elem constructing noisy 4 name array elem,constructing noisy 5 name array elem destructing noisy 4 name array elem destructing noisy 3 name array elem destructing noisy 2 name array elem destructing noisy 1 name array elem noisy:delete destructing noisy 0 name before array caught 5 testing unexpected: constructing noisy 6 name before unexpected constructing noisy 7 name z destructing noisy 6 name before unexpected caught c 分析上述結(jié)果不難看出:, 構(gòu)造函數(shù)在兩種情況下會發(fā)生異常拋出。 第一種情況是當(dāng)?shù)谖鍌€對象被創(chuàng)建時,被拋出的異 常對象是一個整數(shù),該異常類型在構(gòu)造函數(shù)的異常 規(guī)格說明中已經(jīng)聲明。 第二種情況是當(dāng)參數(shù)字符串的第一個字符為“z”時將 拋出一字符型異常,由于該異常類型在構(gòu)造函數(shù)的 異常規(guī)格說明中未聲明,所以導(dǎo)致 unexpected() 被 調(diào)用。注意,如果用 catch(char c) 或 catch() 替換 catch(int c),則 unexpected() 將不會被調(diào)用。, 第一種情況的異常發(fā)生時,第 0,1,2,3,4 個對 象都已被完整創(chuàng)建,所以異常處理器將調(diào)用它們的 析構(gòu)函數(shù)清除它們。而第 5 個對象在創(chuàng)建中拋出了 異常對象,所以它沒有被完整創(chuàng)建,因此異常處理 器不能調(diào)用它的析構(gòu)函數(shù)清除它。 第二種情況的異常發(fā)生時,第 6 個對象都已被完整 創(chuàng)建,所以異常處理器將調(diào)用它的析構(gòu)函數(shù)清除 它。而第 7 個對象在創(chuàng)建中拋出了構(gòu)造函數(shù)的異常 規(guī)格說明中未聲明的異常,導(dǎo)致 unexpected() 被調(diào) 用,所以該對象沒有被完整創(chuàng)建,因此異常處理器 不能調(diào)用它的析構(gòu)函數(shù)清除它。, 異常發(fā)生時還沒有被創(chuàng)建的對象(第一種情況時的 對象 array5,array6 和 n2 以及第二種情況時的對 象 n5 )均不再被創(chuàng)建。 對于類 noisy,運(yùn)算符 new 和 delete 均被重載。 函數(shù) unexpected_rethrow 可拋出所有類的異常,所以 該函數(shù)再次拋出與已知類型完全相同的異常。這樣 函數(shù) unexpected_rethrow 的作用就是接收任何未加說 明的異常對象,并作為已知異常對象再次拋出。通 過這種方法,該函數(shù)可作為濾波器用以跟蹤意外異 常的出現(xiàn)并獲取該異常對象的類型。,9.6 構(gòu)造函數(shù) 從上一章的討論可知,如果構(gòu)造函數(shù)中出現(xiàn)異常, 這將使得類對象不能被完整創(chuàng)建,從而導(dǎo)致類對象需 要撤消時析構(gòu)函數(shù)不能被正常調(diào)用,使類對象無法被 撤消。這意味著在編寫類的構(gòu)造函數(shù)時,必須十分慎 重地對待是否會有異常發(fā)生。另一方面,構(gòu)造函數(shù)進(jìn) 行存儲資源的動態(tài)分配是經(jīng)常發(fā)生的,而在存儲資源 的動態(tài)分配過程中可能有異常發(fā)生。如果用于動態(tài)分 配存儲資源的指針是未加保護(hù)的,則析構(gòu)函數(shù)將無法 收回這些存儲資源。,例9-5 描述了在對象構(gòu)造過程中出現(xiàn)異常引起的錯誤 運(yùn)行結(jié)果產(chǎn)生的文件 nudep.out 中保存了如下信息: useResources() bonk() bonk() bonk() allocating an og inside handler 分析結(jié)果不難看出:,類 useResources 的構(gòu)造函數(shù)執(zhí)行過程中,當(dāng)通過指針 屬性 bp 動態(tài)分配 bonk 類型數(shù)組空間時, bonk 類型的 構(gòu)造函數(shù)被調(diào)用不會拋出異常對象。而當(dāng)通過指針屬 性 op 動態(tài)分配 og 類型對象時,則 og:operator new 的 執(zhí)行中會拋出一個異常對象。這就使得程序在異常處 理器中被意外地結(jié)束,而 useResources 的析構(gòu)函數(shù)未 能得到調(diào)用。這是因?yàn)楫惓0l(fā)生時,useResources 構(gòu)造 函數(shù)的全部工作沒未完成,使得 useResources 對象所 有的內(nèi)存空間,包括已經(jīng)被完整創(chuàng)建的 bonk 類型的數(shù) 組空間都不能被清除。,內(nèi)存動態(tài)分配操作的對象化: 為了防止上述情況的發(fā)生,應(yīng)避免對象通過本身的 構(gòu)造函數(shù)將 “不完整” 的資源分配到對象中。所謂對象 化方法是將每個分配操作變成了 “原子型” 的,像一個 類對象,只要是成功分配資源的 “原子型” 對象都能被 正確地清除。模板是實(shí)現(xiàn)內(nèi)存動態(tài)分配操作對象化的 一種好方法。 例9-6 展示了對例9-5 進(jìn)行內(nèi)存動態(tài)分配操作對象化 修改所產(chǎn)生的結(jié)果。 運(yùn)行結(jié)果產(chǎn)生的文件 wrapped.out 中保存了如下信息:,bonk() bonk() bonk() pwrap constructor allocating an og bonk() bonk() bonk() pwrap destructor inside handler,分析結(jié)果: 不同點(diǎn)是使用類模板 pwrap 封裝內(nèi)存資源的動態(tài)分 配操作,并使用 pwrap 實(shí)例定義 useResources 的對象 成員 Bonk 和 Og,替代原來的 bonk 類型指針 bp 和 og 類型指針 op。在 useResources 對象的構(gòu)造過程中,模 板 pwrap 的構(gòu)造函數(shù)被調(diào)用,如果這些模板構(gòu)造函數(shù) 所進(jìn)行的內(nèi)存資源的動態(tài)分配操作完成之后發(fā)生了異 常,已分配內(nèi)存資源就能夠被模板析構(gòu)函數(shù)清除。因 此,對象成員 Og 創(chuàng)建時,存儲空間分配操作所發(fā)生的 異常不會影響為 Bonk 對象數(shù)組動態(tài)分配的內(nèi)存空間被 清除,沒有內(nèi)存泄漏。,9.7 異常對象的匹配 當(dāng)一個異常對象拋出時,異常處理器會根據(jù)被拋出 異常對象的類型順序匹配 “最近” 的異常處理分支。 異常對象的匹配并不要求被拋出的異常對象的類型 和異常處理分支的參數(shù)類型完全一致。如果一個被拋 出的異常對象的類型是某個類的派生類,則在異常處 理器中允許用一個使用基類為參數(shù)的異常處理分支與 其匹配。但需要注意的是:, 若處理器的參數(shù)是基類對象,而不是基類對象的引 用或指針,派生類異常對象在與異常處理器匹配的 過程中將會被 “切片”,被 “切片” 的異常對象雖然不 會受到破壞,但會丟失所有派生類型的新增特征。 系統(tǒng)預(yù)定義類型的異常對象在匹配過程中,可以發(fā) 生類型的隱含轉(zhuǎn)換,而自定義類型的異常對象在匹 配過程中,不會發(fā)生類型的隱含轉(zhuǎn)換。 例9-7 描述了在自定義類型的異常對象匹配過程中不 會自動執(zhí)行類型的轉(zhuǎn)換。,分析結(jié)果: 盡管我們已經(jīng)通過類 except2 的構(gòu)造函數(shù)提供了將類 except1 對象隱含轉(zhuǎn)換為 except2 對象的功能,但是當(dāng) except1 對象作為異常對象被拋出后,在與異常處理器 的第一個異常處理分支匹配過程時,不會被隱含轉(zhuǎn)換 為 except2 對象。 例9-8 描述了在異常處理器中如何使用基類參數(shù)捕獲 派生類的異常對象。,分析結(jié)果: 第一個異常處理分支總能匹配一個 trouble 對象或從 trouble 派生的類對象; 由于第一個異常處理分支捕獲了能與第二和第三個 異常處理分支的所有異常對象,導(dǎo)致第二和第三個 異常處理分支永遠(yuǎn)不會被調(diào)用。 異常處理器中的異常對象匹配順序應(yīng)該先匹配派生 類異常,而把基類異常的匹配放在最后更有意義; 由于派生類 small 和 big 的對象比基類 trouble 的對象 大,因此在第一個異常處理分支中它們將被 “切片” 以適應(yīng)異常對象匹配的需要。避免這種信息的剪裁 應(yīng)該使用基類的引用或指針作為匹配參數(shù)。,9.8 標(biāo)準(zhǔn)異常 在 C+ 標(biāo)準(zhǔn)庫中提供了一批標(biāo)準(zhǔn)異常類,為用戶在 編程中直接使用和作為派生異常類的基類。下面的三 張表描述了這些標(biāo)準(zhǔn)異常類:,其中異?;?exception 的定義如下: class _CRTIMP exception public: exception(); exception(const _exString 它的派生類,以 logic_error 為例,其定義如下:,class _CRTIMP logic_error : public exception public: explicit logic_error(const string,9.9 含有異常的程序設(shè)計(jì) 在本章中將論述使用異常處理機(jī)制和方法進(jìn)行程序 設(shè)計(jì)的一些常用原則。 9.9.1 何時避免使用異常處理 異常處理并不是解決所有運(yùn)行錯誤的的最佳方法。 下面是各種不適合使用異常處理的情況: 1 異步事件 標(biāo)準(zhǔn) C 的信號系統(tǒng) signal 以及其他類似的控制系統(tǒng) 產(chǎn)生的異步事件。 異步事件發(fā)生在程序控制的范圍以外,它必須有 完全獨(dú)立的代碼來處理,而不是程序流的一部 分,它的發(fā)生是程序所不能預(yù)計(jì)的。, 異常的發(fā)生和異常的處理處于相同的運(yùn)行環(huán)境 中,即異常被限制在一定范圍內(nèi)。 在一些定義明確的程序點(diǎn)上,一個異常可以以基 于中斷的方式拋出。 2 普通錯誤情況 如果一個錯誤發(fā)生時有足夠的信息去處理它,這個 錯誤就應(yīng)引起一個異常。此時應(yīng)該關(guān)心當(dāng)前運(yùn)行環(huán) 境,而不應(yīng)該創(chuàng)建一個描述錯誤的異常對象,并將 它拋出當(dāng)前的運(yùn)行環(huán)境。例如,不應(yīng)當(dāng)為機(jī)器層的 事件(如 “除零溢出”)拋出異常,這些異常可由其 他機(jī)制(如操作系統(tǒng)或硬件)去處理。,3 流控制 雖然異常和處理看上去有點(diǎn)像一個交替返回機(jī)制, 也有點(diǎn)像一個 switch 語句段,但我們不能用這種機(jī) 制去控制程序流,而改變了使用該機(jī)制的初衷。 4 不強(qiáng)迫使用異常 一些程序相當(dāng)簡單,例如一些應(yīng)用程序可能僅僅需 要獲取輸入和執(zhí)行一些加工。如果在這類程序中試 圖分配存儲或使用文件操作等,則可以使用 assert() 顯示出錯信息,使用 abort() 終止程序。而刻意使用 異常處理方法進(jìn)行異常對象的創(chuàng)建、拋出、捕獲來 實(shí)現(xiàn)系統(tǒng)資源修復(fù),則是不明智的。 從根本上說,假如不需要使用異常,就不要使用。,9.9.2 異常處理的典型使用 使用異常處理的優(yōu)點(diǎn)主要表現(xiàn)在: 便于將問題固定下來和重新測試調(diào)用這個導(dǎo)致異常 的函數(shù)。 便于修正由異常對象指示的錯誤,而后繼續(xù)運(yùn)行。 便于計(jì)算一些選擇結(jié)果用于代替函數(shù)假定產(chǎn)生的結(jié) 果。 便于在當(dāng)前運(yùn)行環(huán)境中盡其所能解決運(yùn)行錯誤,并 且便于將不能解決的錯誤拋出的異常對象拋向更高 一層的運(yùn)行環(huán)境。, 便于根據(jù)不能解決的錯誤拋出的異常對象,創(chuàng)建一 個新的相關(guān)的異常對象,并拋向更高一層的運(yùn)行環(huán) 境。 便于終止程序運(yùn)行。 便于包裝使用普通錯誤處理函數(shù)(尤其是 C 的庫函 數(shù)),以便產(chǎn)生異常處理替代。 便于簡化出錯處理。 便于更安全地使用庫和程序。,在程序設(shè)計(jì)中使用異常處理機(jī)制應(yīng)該注意: 1 在函數(shù)聲明中使用異常規(guī)格說明 函數(shù)原型聲明中的異常規(guī)格說明是用戶如何使用 異常處理機(jī)制編寫函數(shù)調(diào)用,處理函數(shù)可能發(fā)生 的運(yùn)行錯誤的程序代碼和編譯器編譯這些代碼的 依據(jù)。 當(dāng)函數(shù)運(yùn)行期間,由于不同的原因拋出了函數(shù)的 異常規(guī)格說明沒有聲明的異常類型對象,則會導(dǎo) 致對 unexpected() 的調(diào)用。因此,在使用異常規(guī)格 說明函數(shù)可能拋出的異常對象類型的同時,一般 也應(yīng)定義用戶定制的 unexpected() 函數(shù)執(zhí)行代碼。,2 基于標(biāo)準(zhǔn)異常類 C+ 的標(biāo)準(zhǔn)異常類應(yīng)該成為用戶在編寫函數(shù)的異 常規(guī)格說明時的首選異常類型。 假若 C+ 的標(biāo)準(zhǔn)異常類不能滿足用戶編程需要, 則應(yīng)該盡量從某個已存在的標(biāo)準(zhǔn)異常類派生定義 用戶自己的異常形成。特別是使用 exception 作為 用戶派生異常類的基類,重新定義該類的虛成員 函數(shù) what() ,這會使用戶受益匪淺。,3 套裝我們自己的異常 如果為我們的特定類創(chuàng)建異常,在類中套裝異常類 是一個好主意,這些異常類僅為我們的特定類使用。還可以防止命名域的混亂。 4 使用異常層次 異常層次為不同類型的重要錯誤分類提供了一個有 價值的方法,這些錯誤可能會與我們的類或庫沖 突。異常層次可為用戶提供有幫助的信息,幫助用 戶組織自己的代碼,選擇是忽略所有異常特定類型 還是正確地捕獲基類類型。異常層次使得任何異常 可通過對相同基類的繼承而追加,以基類為參數(shù)的 異常處理器將捕獲新的異常。C+ 的標(biāo)準(zhǔn)異常類是 一個異常層次的優(yōu)秀范例。,5 多重繼承 當(dāng)需要把一個指向?qū)ο蟮闹羔樝蛏嫌成涞絻蓚€不同 的基類,實(shí)現(xiàn)兩個基類的多態(tài)行為時,使用多重繼 承將是十分有效的。多重繼承對于異常層次也是非常有用的,因?yàn)橐远嘀乩^承異常的任一個基類為參 數(shù)的異常處理器都可以處理多重繼承異常。 6 用異常 “引用” 而非異常 “值” 去捕獲 如果拋出一個派生類對象而且該對象被基類的異常 處理器通過 “值” 捕獲到,對象會被 “切片”,也就是 說,隨著向基類對象的傳遞,派生類元素會被依次 割下,直到傳遞完成。,例9-9 展示了使用 “引用” 將一個異常的派生類對象傳 遞給基類處理器時不被 “切片” 的實(shí)例。 當(dāng)派生類 derived 對象通過 “值” 被捕獲時,被 “切 片” 成基類 base 對象,表現(xiàn)出 base 對象的行為。 當(dāng)派生類 derived 對象的 “引用” 被捕獲時,對象 不會被 “切片”,表現(xiàn)出派生類的真實(shí)情況。 雖然也可以拋出和捕獲指針,但這樣做會引入更 多的耦合 拋出和捕獲器必須為怎樣分配和清 除異常對象達(dá)成一致,這將是一個問題,因?yàn)樾?的異常又可能會由于堆的耗盡而產(chǎn)生。,7 在構(gòu)造函數(shù)中拋出異常 由于構(gòu)造函數(shù)沒有返回值,如果沒有異常機(jī)制,只能按以下兩種選擇報告在構(gòu)造期間的錯誤: 設(shè)置一個非局部的標(biāo)志并希望用戶檢查它。 希望用戶檢查對象是否被完全創(chuàng)建。 這是一個嚴(yán)重的問題,因?yàn)樵?C+ 程序中,對象構(gòu) 造失敗后繼續(xù)執(zhí)行注定是災(zāi)難。所以構(gòu)造函數(shù)成為 拋出異常最重要的用途之一。使用異常機(jī)制是處理 構(gòu)造函數(shù)錯誤的安全有效的方法。然而我們還必須 把注意力集中在對象內(nèi)部的指針上和構(gòu)造函數(shù)異常 拋出時的清除方法上。,8 不要在析構(gòu)函數(shù)中拋出異常 由于析構(gòu)函數(shù)會在拋出異常時被調(diào)用,所以永遠(yuǎn)不 要在析構(gòu)函數(shù)中拋出一個異?;蛘咄ㄟ^執(zhí)行在析構(gòu) 函數(shù)中的動作導(dǎo)致其他異常的拋出。否則就意味著 在已存在的異常到達(dá)引起捕獲之前又拋出一個新的 異常,這會導(dǎo)致對 terminate() 的調(diào)用。換句話說,假 若調(diào)用一個析構(gòu)函數(shù)中的任何函數(shù)都有可能會拋出 異常,則這些調(diào)用應(yīng)該寫在析構(gòu)函數(shù)中的一個測試 塊 try 中,而且析構(gòu)函數(shù)必須自己處理所有自身的異 常,即這里的異常都不應(yīng)逃離析構(gòu)函數(shù)內(nèi)部。,9.10 開銷 使用任何一個新特性必然有所開銷。異常被拋出需 要開銷相當(dāng)?shù)倪\(yùn)行時間,這就是不要把異常處理用于 程序流控制的一部分原因。 相對于程序的正常執(zhí)行,異常是偶而發(fā)生的。因此 設(shè)計(jì)異常處理的重要目標(biāo)之一是:當(dāng)異常沒有發(fā)生 時,異常處理代碼應(yīng)不影響運(yùn)行速度。換句話說,只 要不拋出異常,代

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論