Posted on August 18th, 2008 at 0:38 by fr3@K
New mind set
一直以來 exception handling 與 exception safety 就是 C++ 最重要的課題之一. 正確的 exception handling 真的不是一件跟吃蛋糕一樣容易的事.
但, 它是否一定如此困難, 以致於 只有語言專家才能搞得定? 我認為這與事實相去甚遠.
當然, 前題是要能正視 exception 的存在以及 其在 C++ 中的不可或缺. 的確有人認為 exception 會使程式的複雜度增加 難以被一般 C++ programmer 正確的處理. 也有組織根本就 禁止旗下 C++ programmer 在工作中使用 exception. 讓我用個類比來說明我的看法; 只有接受駕駛汽車帶來的便利性與必要性的人, 才能更有效的學習良好的駕駛技巧, 以享受其好處與減少意外發生.
要知道 exception 在 C++ 語言之中幾是在最核心的位置. 下面這句話不太好聽, IMHO, 不願面對 exception 的 programmer 或許應該避免使用 C++, 轉而擁抱其他的 programming language. 這不是在說酸話, AFAIK, Embedded C++ 就是這樣的語言. 不支持 exception 正是它與 C++ 的最根本的差異.1
與 C 的相容, 或說是重複使用 C 的 library 以及語言本身, 可能是早期把 C++ 推向被大幅使用最大功臣之一. 到了今天, 雖然 C++ 還是可以在某一程度上被當成 better C 來使用, 但 C++ 早已走上一條與 C 不同的道路, 不再只是 C with Object. 不只有 OOP, 更有 meta programming 和 functional programming. 有 RAII (guard 手法) 來侷限 locally allocated resource 的有效範圍. 有 exception safety 來確保例外 (exceptional) 情況發生時程式的完整性 (integrity). 在 C++ 中 reuse C 時, 別把 C 的 programming style 也 reuse 了.
面對 exception, programmer 的 mind set 要改變. 把心態從:
- 只有某些操作會 throw
進化到:
- 只有某些操作不會 throw
也就說就是要具有 “anything can throw, except …” 的心態. 畢竟除了 non-modifying 的操作之外, 可能會拋出 exception 的操作比不會拋出 exception 的多太多了.
Modifying functions those won’t throw
接下來得要培養辨認出哪些 modifying 的操作不會 throw 的能力:
如 如改變 pointer 的值 (讓 pointer 指向另一個 instance), 計算兩 pointer 間的距離等. 注意: 這與操作 pointer 指向的 instance 是兩碼子事. 如 關於這點其實很有趣. 它不是語言層級的限制, 但確是 C++ 語言與 programmer 所遵循的 protocol. 因為任何拋出 exception 的 destructor 都可以很容易地導致整個程式在沒有 unwind 就暴力地結束. 這只是 item 1 and item 2 的延伸. Note: 這裡所謂的 user-defined 除非 programmer 寫錯了, 任何刻意寫出來的 Note: This item was added on Sep. 3rd, 2008 |
Note: 這個列表極可能不夠完整.
Non-modifying functions those throw
Non-modifying 的操作就不會 throw 了嗎? 原則上是的. 但要注意有些 non-modifying 的操作需要使用暫時物件. 如果此暫時物件的生成或是被使用到的 (member) function 有可能拋出 exception, 自然該 non-modifying 操作也是有可能拋出 exception 的. 不論有沒有 source code 可供參考, 文件中的 exception 描述非常重要!
而也有 non-modifying 操作是以拋出 exception 的方式回報錯誤的, 如 vector::at(size_type offset) 就是以拋出 out_of_range 來表示使用者所給的參數 - offset - 大於或等於 vector::size() 的錯誤狀況. 不過, 這類錯誤通常都是程式邏輯錯誤. 也是能避免的.4
除此之外, user-defined implicit conversion (隱式轉型) 是另一個需要注意到的地方. 例如, 常見的在需要 const string& 的地方使用 string literal:
typedef map<string, string> KeyValueMap;
const string* GetEmail(const KeyValueMap& key_values)
{
KeyValueMap::const_iterator email =
key_values.find(“Email”); // implicit conversion!!!
if(email == key_values.end())
return NULL;
return &(email->second);
}
Finally
一旦適應了 “anything can throw, except …” 的思維, 接下來要寫出俱備 exception safety 保證的 code 便不是那麼的困難了, 只要你知道那些操作是不會 throw 的.
前面帶到哪些 modifying 操作不會 throw, 怎麼樣的 non-modifying 操作可能會 (因使用方式而) throw. 其他的就單純了, 請假設它們都有可能會 throw.
Look at exception in its face. Live with it and deal with it! 但可不是讓你緊抱它, 把拋出 exception 當作唯一錯誤回報的真理! 要在正確的場合使用才對. 不過, 那是另外一個題目了.5 我打算在接下來幾篇還沒完稿的文字中, 拿大家都熟知的例子來說明如何以不同的方式編寫, 與俱備不同程度 exception-safety 保證的代碼.
[Update Aug 31st, 2008] 續篇 Exception Handling 新思維, Using Guards.
- 其他的主要差異還有 Embedded C++ 不支持 template 與 multiple inheritance. 詳見 Rationale for the Embedded C++ specification [↩]
- 在許多的情況下, pointer types 可視為 built-in types. [↩]
- 當然, programmer 也可明確要求
T必須具有 no-throw 的 swap [↩] - 可在使用
vector::at(size_type offset)之前先呼叫vector::size()或在使用vector::back()之前呼叫vector::empty()以避免觸動到 exception-throwing 的 code path. [↩] - 在 Google Forbids Use of Exception in C++ 以及後續的討論串中, 有簡單討論到不同的錯誤回報機制的使用時機. [↩]
![]() |
|
| Previous Post « iGoogle, Not Safer than Anything Else? « |
Next Post » Sutter on Hungarian Notation » |








C++ Coding Standards 解釋了不少「為什麼一定要注意 throw」的原理。雖然有些人認為這本書只是在重覆其他相關書籍的內容,不過拿來當 check list 卻十分好用,與您這篇文章有異曲同工之妙。
Comment by augusitnus — August 18, 2008 @ 1:10