Re: [問題] 例外處理

看板C_and_CPP (C/C++)作者 (我要加入劍道社!)時間16年前 (2009/05/31 12:26), 編輯推噓16(1600)
留言16則, 16人參與, 最新討論串4/4 (看更多)
其實只要注意到現代化的 OOP 語言 (C++、Java、C#、Python、Ruby 等), 幾乎都提供 exception 的支援,大概就可以了解 exception 相對於 error code 具有相當大的優勢。 Exception 最重要的優點在於你可以分離「正常程序」與「錯誤處理程序」, 讓你的程式碼變得清楚易懂。 這是一段實際存在的程式碼,來源是一個稱為 Minibase 的教學用資料庫系統。 他們使用 error code 來作為錯誤處理的方式: while( ((SortedPage*)cur_page)->get_type() != LEAF ){ ((BTIndexPage*)cur_page)->get_page_no(key, header.keytype, next_id); st = MINIBASE_BM->unpinPage(cur_id); // 正常程序 if(st != OK) return MINIBASE_CHAIN_ERROR(BTREE, st); // 錯誤處理 st = MINIBASE_BM->pinPage(next_id, cur_page); // 正常程序 if(st != OK) return MINIBASE_CHAIN_ERROR(BTREE, st); // 錯誤處理 cur_id = next_id; } 你會發現,只要你呼叫了任何會回傳 error code 的函式,你都必須在其後 馬上檢查 error code。最糟糕的是,大部份的情況下,你就算發現有 error 發生,但底層函式並不知道該如何處理,只能把錯誤往上層丟,使得你必須 浪費大量的時間去寫重覆的 if(st != OK) return ... 。只要稍微翻一下整 個 Minibase 的原始碼,就會發現一行正常程序拌隨兩行錯誤處理程序,嚴 重影響整體的可讀性。有時甚至可以看到程式設計師偷懶的地方: if ((st = MINIBASE_BM->pinPage(curPageNo, curPage)) != OK) assert(0); // 這行很慘,因為 release mode 不會有 assert error! 這種大量而重覆工作,就應該交給 compiler 來完成。Exception 的好處就在 此:當你的函式不知道怎麼處理錯誤,就不需要寫錯誤處理的程式碼,exception 發生時,會把控制權一層層往上傳遞,直到它被抓住 (catch) 為止。 有板友提到因為 C++ 沒有 GC,因此使用 exception 容易產生 memory leak 的 問題,其實 exception 可能造成的危害不只如此,因為會遺失的「資源」並不只 有記憶體。比如說已建立的網路連線、mutex、檔案寫入鎖定等等,都可能因為 exception 的發生,而導致這些資源持續被占用。GC 只管記憶體,因此無法釋放 這類資源。Java 有 GC,也大量使用 exception 作為錯誤處理,同樣也會發生這 類資源遺失的問題。 事實上,不管你如何處理錯誤,只要你要求「錯誤發生時,應該馬上中斷並回傳到 上一層」,那麼就必需面對資源遺失的問題,即使你使用 error code 亦同。唯一 的不同點在於使用 error code 時,你明確地使用 return 來離開函式,因此你比 較容易確定發生錯誤時的流程,至於 exception 就沒那麼簡單了。 為了確保你的資源不會遺失,一般來說會使用 RAII 的手法:產生一個物件,在建 構式中取得資源,並在解構式中釋放資源。比如說你原本的程式如下: void foo(FILE* f) { flock(fileno(f), LOCK_EX); // lock the file do_something(f); flock(fileno(f), LOCK_UN); // unlock the file } 這段程式碼並非 exception safe,因為 do_something() 產生 exception 時,其後 的 flock 操作並不會被執行,因此該檔案會保持在被鎖定的狀態。RAII 的方法如下 class FileLocker { public: explict FileLocker(FILE* f) : fd(fileno(f)) { flock(fd, LOCK_EX); } ~FileLocker() { flock(fd, LOCK_UN); } private: FileLocker(const FileLocker&); // prevent copy constructor FileLocker& operator=(const FileLocker&); // and copy assignment int fd; }; void foo(FILE* f) { FileLocker lock(f); // lock the file do_something(f); // The dtor of FileLocker will unlock the file automatically } 不管 do_something() 有沒有產生 exception,FileLocker 物件在脫離 foo() 函式時都會進行解構,進而確保檔案會解鎖。而這也是我很鼓勵大家多用 vector, 少用 new 來達成動態陣列的原因: void foo(int n) { int* a = new int[n]; do_something(a); // FIXME: may throw exception! delete [] a; } void bar(int n) { vector<int> a(n); do_something(a); // a will be released even if exception occurs } 說了這麼多,再來說說 exception 的缺點吧。我個人覺得 exception 主要有兩 項缺點: 1. 效率不佳。這邊的效率不僅僅是「compiler 為了這個功能而幫你產生額外的 程式碼」,還包含「你為了達成 exception safety 而犧牲的效能」。在 Exceptional C++ 一書中提到,若你的 class 包含其它物件,而你想寫一個 exception safe copy assignment,多半必須依賴「產生暫時物件」加上 「nothrow swapping」的手法來達成。然而「產生暫時物件」顯然會犧牲效 率,nothrow swapping 則要求所包含的其它物件配置在 heap,同樣也是犧 牲效率。 丟出 exception 讓上層函式接受,是一件很花時間的動作,因為 compiler 多半假設 exception 並不常發生,自然也不太會在這方面尋求最佳化。 2. Exception 需要在軟體設計之初就被納入設計考量內,這也是 Google 不採用 exception 的原因。已寫好、未使用 exception 的程式碼,若想改用 exception 來處理錯誤,往往會打破原本的設計架構。而這樣的程式碼勢必也無法和其它 有使用 exception 的程式碼互相合作。因此 Google 才基於實務考量,全面禁 用 exception。 -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 59.115.149.69

05/31 12:38, , 1F
推:)
05/31 12:38, 1F

05/31 14:00, , 2F
好文
05/31 14:00, 2F
※ 編輯: littleshan 來自: 59.115.149.69 (05/31 15:04)

05/31 15:43, , 3F
該m
05/31 15:43, 3F

05/31 15:56, , 4F
很多看不懂,收藏先
05/31 15:56, 4F

05/31 16:24, , 5F
推:)
05/31 16:24, 5F

05/31 17:05, , 6F
推好文
05/31 17:05, 6F

05/31 17:35, , 7F
05/31 17:35, 7F

05/31 19:23, , 8F
推,收藏起來
05/31 19:23, 8F

05/31 20:23, , 9F
推經典好文
05/31 20:23, 9F

06/01 00:18, , 10F
06/01 00:18, 10F

06/01 02:23, , 11F
好文 推~
06/01 02:23, 11F

06/01 10:30, , 12F
好文 推推
06/01 10:30, 12F
※ 編輯: littleshan 來自: 210.59.52.3 (06/01 10:43)

06/01 13:36, , 13F
推很大
06/01 13:36, 13F

06/03 10:59, , 14F
醍醐灌頂!
06/03 10:59, 14F

10/11 04:05, , 15F
非常棒,太感謝
10/11 04:05, 15F

10/22 11:25, , 16F
好文!剛好在找這方面資料!
10/22 11:25, 16F
文章代碼(AID): #1A8WRrhc (C_and_CPP)
文章代碼(AID): #1A8WRrhc (C_and_CPP)