推 renderer:推 感謝大大這麼有心 :) 222.156.10.167 08/14
※ 引述《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