C++異常處理幾大秘訣
在調(diào)用catch塊之前,把當(dāng)前異常保存在exception_storage對象中,并注冊一個(gè)專用于catch塊的異常處理程序,,C++異常處理程序必須繼續(xù)傳就能得到exception_storage對象。
現(xiàn)在重新回到C++異常處理這個(gè)主題上來。調(diào)用catch塊時(shí),它可能重新拋出異?;驋伋鲂庐惓!G耙环N情況下,C++異常處理程序必須繼續(xù)傳播 (propagate)當(dāng)前異常;后一種情況下,它需要在繼續(xù)之前銷毀原來的異常。
此時(shí),處理程序要面對兩個(gè)難題:"如何知道異常是源于catch塊還是 程序的其他部分"和"如何跟蹤原來的異常"。我的解決方法是:在調(diào)用catch塊之前,把當(dāng)前異常保存在exception_storage對象中,并注 冊一個(gè)專用于catch塊的C++異常處理程序——catch_block_protector。調(diào)用get_exception_storage()函數(shù),就能得到exception_storage對象:
- namespace my_handler
- {
- __declspec(dllexport) exception_storage* get_exception_storage() throw ()
- {
- void * p = TlsGetValue(dwstorage);
- return reinterpret_cast (p);
- }
- }
- BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
- {
- using my_handler::exception_storage;
- exception_storage *p;
- switch (ul_reason_for_call)
- {
- case DLL_PROCESS_ATTACH:
- //主線程(第一個(gè)線程)不會收到DLL_THREAD_ATTACH通知,所以,
- //與其相關(guān)的操作也放在這了
- dwstorage = TlsAlloc();
- if (-1 == dwstorage)
- return FALSE;
- p = new exception_storage();
- TlsSetValue(dwstorage, p);
- break ;
- case DLL_THREAD_ATTACH:
- p = new exception_storage();
- TlsSetValue(dwstorage, p);
- break;
- case DLL_THREAD_DETACH:
- p = my_handler::get_exception_storage();
- delete p;
- break ;
- case DLL_PROCESS_DETACH:
- p = my_handler::get_exception_storage();
- delete p;
- break ;
- }
- return TRUE;
- }
這樣,當(dāng)catch塊(重新)拋出異常時(shí),程序?qū)?zhí)行catch_block_protector。如果是拋出了新異常,這個(gè)函數(shù)可以從 exception_storage對象中分離出前一個(gè)異常并銷毀它;如果是重新拋出原來的異常(可以通過ExceptionInformation數(shù)組 的前兩個(gè)元素知道是新異常還是舊異常,后一種情況下著兩個(gè)元素都是0,參見下面的代碼),就通過拷貝ExceptionInformation數(shù)組來繼續(xù) 傳播它。下面的代碼就是catch_block_protector()函數(shù)的實(shí)現(xiàn)。
在單線程程序中,這是一個(gè)完美的實(shí)現(xiàn)。但在多線程中,這就是個(gè)災(zāi)難了,想象一下多個(gè)線程訪問它,并把異常對象保存在里面的情景吧。由于每個(gè)線程都有自己的 堆棧和C++異常處理鏈,我們需要一個(gè)線程安全的get_exception_storage實(shí)現(xiàn):
每個(gè)線程都有自己單獨(dú)的 exception_storage,它在線程啟動時(shí)被創(chuàng)建,并在結(jié)束時(shí)被銷毀。Windows提供的線程局部存儲(thread local storage,TLS)可以滿足這個(gè)要求,它能讓每個(gè)線程通過一個(gè)全局鍵值來訪問為這個(gè)線程所私有的對象副本,這是通過TlsGetValue()和 TlsSetValue這兩個(gè)API來完成的。
Excptstorage.cpp中給出了get_exception_storage()函數(shù)的實(shí)現(xiàn)。它會被編譯成動態(tài)鏈接庫,因?yàn)槲覀兛梢约酥谰€ 程的創(chuàng)建和退出——系統(tǒng)在這兩種情況下都會調(diào)用所有(當(dāng)前進(jìn)程加載的)dll的DllMain()函數(shù),這讓我們有機(jī)會創(chuàng)建特定于線程的數(shù)據(jù),也就是 exception_storage對象。
【編輯推薦】