使用異常和返回值 1.使用返回值處理錯誤需要程序員嚴格的編程作風,程序員不論是否有這種習慣,這都是非常不希望的。 2.正確的異常處理是C++中的一個常識。異常通過發出錯誤信號,可以讓程序代碼和錯誤處理代碼分開,而且不會讓程序忽略錯誤。 3.必須瞭解使用了哪種錯誤處理方法,返回值還是拋出異常。如果不知道,那麼你的程序肯定有問題。 4.異常是基於每個線程而提出並處理的;異常不能被線程忽略,必須被處理;未處理的異常會使進程結束,而不僅僅是線程結束;異常處理在釋放棧時會釋放所有的棧對象,因此避免了資源的漏洞;異常處理需要大量的額外操作,使得它並不適於經常運行的代碼。詳細的說,catch塊有一些開銷(overhead),但是try塊有很少的開銷;因此只有在拋出異常的時候纔會有很多的異常操作開銷;你可以拋出任何類型的異常對象,但不包括整數。 5.返回值可以指示正常和不正常的函數運行,但不能阻止線程的繼續運行;返回值很容易被忽略;返回值在典型的情況下是一個整數,通常映射符合於一個預定義的值;返回值能高效的傳遞和接收; 6.所有的非錯誤的狀態信息都應該使用返回值 7.返回值用於大多數情況下可以隨意忽略而不會出現問題的錯誤 8.在循環中的錯誤處理必須快速,因爲異常的額外開銷,所以爲了得到更好的性能,使用返回值是一個更好的選擇。在這種情況下,如果你真要使用異常,可以創建一個函數來將返回值轉化未異常。 9.使用於中間語言模塊中的錯誤。 10.使用Windows API的錯誤處理機制。用SetLastError設置錯誤代碼,通過GetLastError檢測這個錯誤代碼。 11.從C++異常處理的觀點看,故障應該被認爲是錯誤。 12.你必須使用/Eha調試器選項來撲獲使用C++異常操作機制的操作系統異常。 14.Windows結構異常處理:使用_try,_except,_finally,_leave關鍵字和RaiseException API函數;由Windows支持,不適於其他操作系統;不處理C++對象的解析;作爲硬件異常或操作系統異常的結果拋出,也可作爲RaiseException函數結果被拋出。 15.C++異常處理:使用try,throw和catch等關鍵字;僅被C++語言支持;處理C++對象的解析;可以拋出任何類型的C++對象。異常對象可以從標準的異常基本類派生,也可以從任何類派生,或者它們也可以是內置的類型;作爲throw語句的結果被拋出。 16.Visual C++ 使用結構異常處理機制實現C++異常。 17.結構異常處理不能處理對象的解析,因此你應該在C++程序中一直使用C++異常。然而,因爲C++異常不能處理硬件和操作系統異常,你的程序需要將結構異常轉化爲C++異常。 18.爲了正確處理硬件和操作系統異常,你可以創建自己的異常類並使用_set_se_translator函數安裝一個結構異常向C++異常的轉化器。 19.不要撲獲那些不能恢復所產生問題的轉化後的結構異常。 20.在很少拋出異常的情況下使用異常的代價並不是很大,而且這樣做確實可以提高性能。 21.異常策略中最重要的一部分實際上就是有一個策略。不要在事後彌補。 22.異常撲獲規則:撲獲處理器按順序提供;如果撲獲處理器撲獲了同一類型或指向同一類型拋出對象的指針,則應撲獲異常。如果撲獲處理器撲獲了一個公共基類或者指向一個公共基類拋出對象的指針,則應撲獲異常;一個省略撲獲處理器撲獲任何類型的異常,因此它總是放在最後。 23.定義一個異常基類來處理程序代碼拋出的異常。 class CProgramException : public exception { public: CProgramException (const _exString &_what_arg) : exception(_what_arg) {} }; 使用CProgramException類使得異常處理更加簡單,因爲可以通過處理這種基類撲獲所有的程序中的異常。如果需要的話,也可以使用額外的成員數據全面描述特定的問題。 24.使用auto_ptr或者一個類似的指針類通過限制局部變量的動態分配來自動釋放資源: void LeakFreeFunction (int arg) { auto_ptr<CMyObject> pObject(new CMyObject(arg)); ... // do something that throws an exception // can still call member functions as normal pObject->MemberFunction(); // no need to delete pObject } 注意:auto_ptr僅在使用delete釋放資源時使用。 25.使用異常處理更簡單,更可靠,更有效,可以創建更健壯的代碼。然而,你應該只在意外的情況下使用異常處理。如果你認爲一個指針應該時空值,這種條件下就直接在代碼中檢查這個值,而不要使用異常。 26.非MFC的C++異常應該通過引用來撲獲。使用引用撲獲異常不需要刪除異常對象(因爲使用引用撲獲的異常會在棧中傳送),而且它保留了多態性(因此你撲獲的異常對象正是你拋出的異常對象)。使用指針撲獲異常需要你刪除對象,而使用值撲獲對象會導致對象的“分片”(slicing),也就是說,將派生的異常對象轉化爲撲獲的數據類型。 27.MFC異常應該通過指針來撲獲。因爲它們通常從堆中分配,當你處理完異常之後,你需要調用Delete成員函數: ... catch (CFileException *e) { // handle file exception ... e->Delete(); // required to prevent a memory leak } 因此,你不可以使用省略撲獲處理器撲獲MFC異常,因爲者會導致一個內存泄漏。你必須使用Delete成員函數刪除MFC異常,而不要用delete操作符,因爲一些MFC異常作爲靜態對象創建。 28.一旦撲獲了異常,你可以通過執行下列典型動作的一些組合來處理它: (1) 什麼也不要做。 (2) 修改這個問題並重試代碼。 (3) 修改這個問題但不要重試代碼。 (4) 如果用戶需要的話,向用戶顯示錯誤信息。 (5) 如果出現的問題不是程序錯誤的話,輸出一個診斷的跟蹤消息。 (6) 如果出現的問題是程序錯誤,輸出一個斷言。 (7) 在日誌文件中記錄這個問題。 (8) 如果異常是不可恢復的,停止進程的運行。 (9) 整理已分配的資源。 (10) 重新拋出這個異常,使得高級函數也能處理這個異常,特別是在當前函數不能完全解決的情況下。你可以重新拋出同一個異常對象,或拋出一個新的異常對象。 29.和/EHa相對的是同步異常(/EHs),而不是/GX。/GX實際上是/EHsc的簡化形式。/GX表示編譯器應該假設extern "C" 的函數不拋出C++異常,而/EHs則拋出。 30.爲用戶和調用環境記錄異常。通常,異常對象類型用於向調用環境通知出現的問題,而問題的描述字符串用於向用戶通知。 31.Visual C++的默認情況下,new和malloc對於錯誤不會拋出異常,但你可以通過使用_set_new_handler安裝一個處理器,讓new針對錯誤拋出異常。你也可以讓malloc通過調用_set_new_mode使用同一處理器。 #include <new.h>
class bad_alloc : public exception { public: bad_alloc(const __exString& what_arg) : exception (what_arg) {} };
int NewHandler (size_t size) { throw bad_alloc("Operator new couldn't allocate memory"); return 0; }
int APIENTRY WinMain (HINSTANCE hInstance,HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow) { _set_new_handler(NewHandler); _set_new_mode(1); // use NewHandler for malloc as well ... }
32.如果已存在的代碼中沒有設定new返回空值,那麼你應該始終讓new出錯時拋出一個異常。 33.浮點數和整數不一樣,在默認情況下它被零除不會出現異常,但是會出現一個非常奇怪的值"1.#INFO" (它表示這個值並不是一個數字)。要讓檢測浮點數問題更簡單一些,你應該用如下的代碼讓浮點數錯誤拋出異常。 #include <float.h> int cw=controlfp(0,0); cw &= ~(EM_OVERFLOW | EM_UNDERFLOW | EM_INEXACT | EM_ZERODIVIDE | EM_DENORMAL | EM_INVALID); _controlfp(cw, MCW_EM); 浮點數異常處理器必須調用_clearfp作爲它的第一條指令來清空浮點數異常。
|