處理固有語言侷限性的四種技術
處理 C++ 中的異常會在語言級別上遇到少許隱含限制,但在某些情況下,您可以繞過它們。學習各種利用異常的方法,您就可以生產更可靠的應用程式。
保留異常來源資訊
在 C++中,無論何時在處理程式內捕獲一個異常,關於該異常來源的資訊都是不為人知的。異常的具體來源可以提供許多更好地處理該異常的重要資訊,或者提供一些可以附加到錯誤日誌的資訊,以便以後進行分析。
為了解決這一問題,可以在丟擲異常語句期間,在異常物件的建構函式中生成一個堆疊跟蹤。ExceptionTracer
是示範這種行為的一個類。
清單 1. 在異常物件建構函式中生成一個堆疊跟蹤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// Sample Program: // Compiler: gcc 3.2.3 20030502 // Linux: Red Hat #include <execinfo.h> #include <signal.h> #include <exception> #include <iostream> using namespace std; ///////////////////////////////////////////// class ExceptionTracer { public: ExceptionTracer() { void * array[25]; int nSize = backtrace(array, 25); char ** symbols = backtrace_symbols(array, nSize); for (int i = 0; i < nSize; i++) { cout << symbols[i] << endl; } free(symbols); } }; |
管理訊號
每當程式執行一個令人討厭的動作,以致於 Linux™ 核心發出一個訊號時,該訊號都必須被處理。訊號處理程式通常會釋放一些重要資源並終止應用程式。在這種情況下,堆疊上的所有物件例項都處於未破壞狀態。另一方面,如果這些訊號被轉換成 C++ 異常,那麼您可以優雅地呼叫其建構函式,並安排多層 catch 塊,以便更好地處理這些訊號。
清單 2 中定義的 SignalExceptionClass
,提供了表示核心可能發出訊號的 C++ 異常的抽象。SignalTranslator
是一個基於 SignalExceptionClass
的模板類,它通常用來實現到 C++ 異常的轉換。在任何瞬間,只能有一個訊號處理程式處理一個活動程式的一個訊號。因此,SignalTranslator
採用了 singleton 設計模式。整體概念通過用於 SIGSEGV 的 SegmentationFault
類和用於 SIGFPE 的 FloatingPointException
類得到了展示。
清單 2. 將訊號轉換成異常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
template <class SignalExceptionClass> class SignalTranslator { private: class SingleTonTranslator { public: SingleTonTranslator() { signal(SignalExceptionClass::GetSignalNumber(), SignalHandler); } static void SignalHandler(int) { throw SignalExceptionClass(); } }; public: SignalTranslator() { static SingleTonTranslator s_objTranslator; } }; // An example for SIGSEGV class SegmentationFault : public ExceptionTracer, public exception { public: static int GetSignalNumber() {return SIGSEGV;} }; SignalTranslator<SegmentationFault> g_objSegmentationFaultTranslator; // An example for SIGFPE class FloatingPointException : public ExceptionTracer, public exception { public: static int GetSignalNumber() {return SIGFPE;} }; SignalTranslator<FloatingPointException> g_objFloatingPointExceptionTranslator; |
管理建構函式和解構函式中的異常
在全域性(靜態全域性)變數的構造和析構期間,每個 ANSI C++ 都捕獲到異常是不可能的。因此,ANSI C++ 不建議在那些其例項可能被定義為全域性例項(靜態全域性例項)的類的建構函式和解構函式中丟擲異常。換一種說法就是永遠都不要為那些其建構函式和解構函式可能丟擲異常的類定義全域性(靜態全域性)例項。不過,如果假定有一個特定編譯器和一個特定系統,那麼可能可以這樣做,幸運的是,對於 Linux 上的 GCC,恰好是這種情況。
使用 ExceptionHandler
類可以展示這一點,該類也採用了 singleton 設計模式。其建構函式註冊了一個未捕獲的處理程式。因為每次只能有一個未捕獲的處理程式處理一個活動程式,建構函式應該只被呼叫一次,因此要採用 singleton 模式。應該在定義有問題的實際全域性(靜態全域性)變數之前定義 ExceptionHandler
的全域性(靜態全域性)例項。
清單 3. 處理建構函式中的異常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
class ExceptionHandler { private: class SingleTonHandler { public: SingleTonHandler() { set_terminate(Handler); } static void Handler() { // Exception from construction/destruction of global variables try { // re-throw throw; } catch (SegmentationFault &) { cout << "SegmentationFault" << endl; } catch (FloatingPointException &) { cout << "FloatingPointException" << endl; } catch (...) { cout << "Unknown Exception" << endl; } //if this is a thread performing some core activity abort(); // else if this is a thread used to service requests // pthread_exit(); } }; public: ExceptionHandler() { static SingleTonHandler s_objHandler; } }; ////////////////////////////////////////////////////////////////////////// class A { public: A() { //int i = 0, j = 1/i; *(int *)0 = 0; } }; // Before defining any global variable, we define a dummy instance // of ExceptionHandler object to make sure that // ExceptionHandler::SingleTonHandler::SingleTonHandler() is invoked ExceptionHandler g_objExceptionHandler; A g_a; ////////////////////////////////////////////////////////////////////////// int main(int argc, char* argv[]) { return 0; } |
處理多執行緒程式中的異常
有時一些異常沒有被捕獲,這將造成程式異常中止。不過很多時候,程式包含多個執行緒,其中少數執行緒執行核心應用程式邏輯,同時,其餘執行緒為外部請求提供服務。如果服務執行緒因程式設計錯誤而沒有處理某個異常,則會造成整個應用程式崩潰。這一點可能是不受人們歡迎的,因為它會通過嚮應用程式傳送不合法的請求而助長拒絕服務攻擊。為了避免這一點,未捕獲處理程式可以決定是請求異常中止呼叫,還是請求執行緒退出呼叫。清單 3 中 ExceptionHandler::SingleTonHandler::Handler()
函式的末尾處展示了該處理程式。
結束語
我簡單地討論了少許 C++ 程式設計設計模式,以便更好地執行以下任務:
- 在丟擲異常的時候追蹤異常的來源。
- 將訊號從核心程式轉換成 C++ 異常。
- 捕獲構造和/或析構全域性變數期間丟擲的異常。
- 多執行緒程式中的異常處理。
我希望您能採用這些技巧中的一些來開發無憂程式碼。