精華區beta C_and_CPP 關於我們 聯絡資訊
※ 引述《renderer (rendering)》之銘言: : 學 C++ 有一段時間了 : 也看過 Exceptional C++ : 但自己卻從未把 C++ Exception 處理機制用在自己的專案裡 : 最近常在想這個問題 : 想來想去 : C++ Exception 有個問題點 : : 對於一個會發生 Exception 的 Function : C++ Compiler 不會要求 Client 一定要把它 try catch 起來 : 當然啦 .h 檔裡又沒有這個訊息 我們無法要求 Compiler 做這件事 : ( Java 則不同 Java 會要求 Client 一定要為此做 try catch ) : 這點會讓隔層才 try catch 的情況發生 : 如 FunA 呼叫 FunB, FunB 呼叫 FunC : FunC 產生 exception : 但在 FunA 才作 try catch : 這有兩個問題點 : 1. 有違封裝性 : FunA 呼叫的又不是 FunC 為什麼是由它來處理呢 : 隔一層是還好 如果隔了好幾層呢 : FunA 從何去知道有哪些 exception 會發生呢 : 2. 讓 exception safety 的寫作與維護(尤其是維護)顯得極難 : memory leak 或 resource leak 十分有可能在中間層發生 : 假如 FunC 本來不會丟出 exception : FunB 也認定 FunC 不會發生 exception : 而處於高層的 FunA 則責任性地 catch(...) : 哪天 FunC 的作者發現不行了 而讓 FunC 丟出 exception : FunB 如果不知情 沒有重新考量在新的情況下 : memory/resource leak 是否會因此而發生 : exception safety 的美夢很有可能就此幻滅 : 尤其是中間隔了好幾層的時候 這種問題更是可怕 : 我們很難要求 team 裡的 programmer 都懂 C++ exception safety 的寫作 : 讓 FunB 不管在那一點中斷都不會有 memory/resource leak 的發生 : 程式邏輯也會正確 roll back : 即使 programmer 有這個能力 : programmer 也不見得這麼有心力讓每一個 function 都如此完美 : 反之 Compiler 如果能給個小提示 exception safety 就會容易得多 : 每一層都會被強迫 得注意 exception 可能丟出的點 : 如此做才能有效減少 programmer 的負擔 : 不知道板上有沒有大大曾經為 C++ 專案建立起完善的 exception 處理機制過 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 這個大概沒人敢應承吧。 : 小弟很想聽聽大大們的經驗談 感謝 :) 講一下 C++ exception 機制的基本觀念。同樣的,我都是用例子來說明。 假設有一個函式 f,以傳統 C 的概念設計如下: int f() { int ret; ... // 中間做了某些重要的工作 return ret; } 然後,有另一個函式 g 如下: void g() { ... f(); // 呼叫函式 f g2(); // 繼續其他的工作 ... } 函式 g 沒有檢查 f 的傳回值,就繼續處理 g2,且之後可能繼續處 理 g3, g4, g5, .... 函式 g, g2, g3, ...的設計者可能與 f 是同一個人,也可能是不 同的人,假設到了 g10 的時候,程式發生問題了,而產生問題的起 因是由於函式 g 沒有檢查函式 f 的回傳值,就一直將後續的工作 委派下去。錯誤雖然 g10 才發生,但其實早在 f 就已經出問題了。 很明顯的,要避免這種「將 bug 無限發散」的情況(當錯誤被發現 的地點,離它真正發生的地點愈遠,除錯的成本就愈大),最好的辦 法是強迫呼叫函式 f 的模組,有義務要檢查 f 的回傳值。那要怎麼 做呢? 以下是一種方法,就是提供 f 的設計者,在其介面(通常是放在標 頭檔)的部份,加上說明,例如: int f(); // 注意!!必須檢查回傳值是否正確!! 但不管註解或說明再怎麼強調甚至恐嚇,相信一定仍有許多使用者照 樣視若無睹,這就像以下經典的例子: int ConstVal = 5; // 我承諾絕對不改變這個變量的值 顯然,無論在註解裏再怎麼承諾,都不如直接在前面加上 const 修 飾,讓編譯器來保證 ConstVal 是 const 的事實,才是一勞永逸的 作法。 同樣的,假設呼叫端有義務檢查 f 的回傳值,那麼一勞永逸的作法 ,就是當 f 的任務失敗時,以拋出異常的方式處理,而不是回傳某 個特殊值表示任務失敗: class Exception { ... }; int f() // 利用 C++ Exception 的機制 { int ret = 1; ... if (ret==0) { Exception e; throw e; } return ret; } 如此一來,不管 g(或任何呼叫 f 的模組)有沒有檢查 f 的回傳值, 一旦 f 的任務失敗,它會立刻丟出一個 exception,不讓 bug 繼續發 散下去。 至於,在函式 g 的部份,如果 f 的任務不管成功或失敗,都不影響後 續的工作,那麼就可以使用 try catch 來補捉 f 拋出的異常。 void g() // 當 g 有能力處理 f 拋出的異常時 { ... try { f(); } catch (Exception &e) { ... // f 任務失敗時的處理 } ... // 其他的工作 } 相反的,如果一旦 f 的任務失敗,g 就沒有辦法繼續工作的話,那 它就不要處理 f 拋出的異常,把處理的工作交給更上層(呼叫 g ) 的模組。 void g() // 當 g 沒有能力處理 f 拋出的異常時,就不要處理 { // 設計和最初一樣 } C++ exception 機制基本的概念是,一旦拋出的異常沒有被處理,程 式就會被迫中斷,這初看不太方便,但其實是避免 bug 擴散的有力 手段。一個稍具規模的專案,花在除錯的時間,通常是設計以及編寫 程式碼時間的三倍以上,因此有經驗的程式設計員,他們生產效率高 的原因,不僅在於編程能力,更重要的是,他們了解如何避免難以尋 找的錯誤。 總之,C++ exception 的基本觀念是「讓有能力處理異常的模組去處 理」。 -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 59.120.214.120
renderer:推 感謝大大這麼有心 :) 222.156.10.167 08/14