C與C++中的異常處理17 (轉)
:namespace prefix = o ns = "urn:schemas--com::office" />
我在Part2介紹了Structured Exception Handling(簡稱SEH)。在那時我就說過,SEH是window及其平臺上的專有的。它不是定義在ISO C++標準中的,使用它的將不能跨編譯器移植。因為我注重於標準相容和可移植性,所以我對將專有的SEH對映為ISO標準C++的exception handing(簡稱EH)很感興趣。
同時,我不是SEH的專家。對它的瞭解絕大部分來自於本專欄前面的研究。當我考慮混合使用SEH與EH時,我猜想解決方法應該是困難的和不是顯而易見的。這是它花了我兩個星期的原因:我預料到需要額外的時間來研究和試驗。
很高興,我完全錯了。我不知道的是Visual C++執行期庫直接支援了絕大部分我所想要的東西。不用創造新的方法了,我可以展示你Visual C++已經支援了的東西,以及改造為所需要的東西的方法。基於這個目的,我將研究同一個例子的四個不同版本。
1.1 Version 1:定義一個轉換
捆綁SEH和EH的方法分兩步:
l 一個自定義的轉換函式來捕獲SEH的異常並將它對映為C++的異常。
l 一個Visual C++執行期庫函式來這個轉換函式
使用者自定義的轉換函式必要有如下形式:
void my_translator(unsigned code, EXCEPTION_POINTERS *info);
轉換函式接受一個SEH異常(透過給定的異常code和info來定義的)。然後丟擲一個C++異常,以此將傳入的SEH異常對映為向外傳的C++異常。這個C++異常將出現在原來的SEH異常發生點上並向外傳播。
這個機制非常象std::set_tenate()和std::set_unexpected()。要安裝轉換函式,要Visual C++庫函式_set_se_translator()。這個函式申明在頭eh.h中:
typedef void (*_se_translator_function)(unsigned, EXCEPTION_POINTERS *);
_se_translator_function _set_se_translator(_se_translator_function);
它接受一個指向新轉換函式的指標,返回上次安裝的指標。一旦安裝了一個轉換函式,前一次的就丟失了;任何時候只有一個轉換函式有效。(在多執行緒程式中,每個執行緒有一個獨立的轉換函式。)
如果還沒安裝過轉換函式,第一次呼叫_set_se_translator()返回值可能是(也可能不是)NULL。也就是說,不能不分青紅皂白就透過其返回的指標呼叫函式。很有趣的,如果返回值是NULL,而你又透過此NULL呼叫函式,將產生一個SEH異常,並且進入你剛剛安裝的轉換函式。
一個簡單的例子:
#include
using namespace std;
int main()
{
try
{
*(int *) 0 = 0; // generate Structured Exception
}
catch (unsigned exception)
{
cout << "caught C++ exception " << hex << exception << endl;
}
return 0;
}
執行它的話,這個控制檯程式將導致如此一個ssagebox:
它是由於一個未被捕獲的SEH異常傳遞到程式外面造成的。
現在,增加一個異常轉換函式,並將Visual C++執行庫設為使用這個轉換函式:
#include
using namespace std;
#include "windows.h"
static void my_translator(unsigned code, EXCEPTION_POINTERS *)
{
throw code;
}
int main()
{
_set_se_translator(my_translator);
try
{
*(int *) 0 = 0; // generate Structured Exception
}
catch (unsigned exception)
{
cout << "caught C++ exception " << hex << exception << endl;
}
return 0;
}
再執行程式。現在將看到:
caught C++ exception c0000005
my_translator()截獲了SEH異常,並轉換為C++異常,其型別為unsigned,內容為SEH異常碼(本例中為C0000005h,它是一個讀取錯誤)。因為這個C++異常出現在原來的SEH異常發生點,也就說在try塊中,所以被try塊的異常處理函式捕獲了。
1.2 Version 2:定義一個轉換
上面的例子非常簡單,將每個SEH異常轉換為一個unsigned值。實際上,你可能需要一個比較複雜的異常物件:
#include
using namespace std;
//#include "windows.h"
#include "structured_exception.h"
/*static void my_translator(unsigned code, EXCEPTION_POINTERS *)
{
throw code;
}*/
int main()
{
//_set_se_translator(my_translator);
structured_exception::install();
try
{
*(int *) 0 = 0; // generate Structured Exception
}
catch (structured_exception const &exception)
{
cout << "caught C++ exception " << hex << exception.what()
<< " thrown from " << exception.where() << endl;
}
return 0;
}
這個例子丟擲了一個使用者自定義型別(structured_exception)的C++異常。為了讓這個例子更具實際意義,也更方便閱讀,我將structured_exception的申明放到了標頭檔案structured_exception.h中:
#if !defined INC_structured_exception_
#define INC_structured_exception_
#include "windows.h"
class structured_exception
{
public:
structured_exception(EXCEPTION_POINTERS const &) throw();
static void install() throw();
unsigned what() const throw();
void const *where() const throw();
private:
void const *address_;
unsigned code_;
};
#endif // !defined INC_structured_exception_
其實現檔案為:
#include "structured_exception.h"
#include "eh.h"
//
// ::
//
static void my_translator(unsigned, EXCEPTION_POINTERS *info)
{
throw structured_exception(*info);
}
//
// structured_exception::
//
structured_exception::structured_exception
(EXCEPTION_POINTERS const &info) throw()
{
EXCEPTION_RECORD const &exception = *(info.ExceptionRecord);
address_ = exception.ExceptionAddress;
code_ = exception.ExceptionCode;
}
void structured_exception::install() throw()
{
_set_se_translator(my_translator);
}
unsigned structured_exception::what() const throw()
{
return code_;
}
void const *structured_exception::where() const throw()
{
return address_;
}
這些函式的意義是:
l my_translator()是異常轉換函式。我把它從main檔案中移到這兒。於是,main檔案不再需要包含windows.h了。
l install()將執行器庫的全域性轉換函式設定為my_translator()。
l structured_exception的建構函式接收並解析SEH異常的資訊。
l what()返回SEH異常的異常碼。
l where()返回SEH異常發生的地點。注意,where()的返回型別是void const *,雖然C++標準不同意將程式碼地址轉換為void指標。我只是重複了Micorsoft的用法,因為Visual C++執行庫將地址存在了SEH異常的EXCEPTION_RECORD的一個void *成員中了。
編譯並連結這三個檔案。執行結果是:
caught C++ exception c0000005 thrown from 0040181D
(其中的程式碼地址值在你的上可能有所不同。)
1.3 Version 3:模仿C++標準執行庫
在my_translator()中,所有的SEH異常對映為同樣的structured_exception型別。這使得異常容易被捕獲,因為它們匹配於我們的唯一的異常處理函式:
catch (structured_exception const &exception)
雖然捕獲了異常,但我們沒有辦法事先知道異常的型別。唯一能做的是執行期查詢,呼叫這個異常的what()成員:
catch (structured_exception const &exception)
{
switch (exception.what())
{
case EXCEPTION_ACCESS_VIOLATION:
// ...
case EXCEPTION_INT_DIV_BY_ZERO:
// ...
case EXCEPTION_STACK_OVERFLOW:
// ...
// ...
}
這樣的查詢需要windows.h中的資訊,以知道最初的SEH異常碼的含意。這樣的需求違背了structured_exception的抽象原則。此外,switch語句也經常違背了多型的原則。從使用者程式碼的角度看,你通常應該用繼承和模板來實現它。
C++標準執行庫在這方面提供了一些指導。如我在Part3中勾畫的,標頭檔案
根據這種精神,standard_exception的申明是:
#if !defined INC_structured_exception_
#define INC_structured_exception_
#include "eh.h"
#include "windows.h"
class structured_exception
{
public:
structured_exception(EXCEPTION_POINTERS const &) throw();
static void install() throw();
virtual char const *what() const throw();
void const *where() const throw();
private:
void const *address_;
//unsigned code_;
};
class access_violation : public structured_exception
{
public:
access_violation(EXCEPTION_POINTERS const &) throw();
virtual char const *what() const throw();
};
class divide_by_zero : public structured_exception
{
public:
divide_by_zero(EXCEPTION_POINTERS const &) throw();
virtual char const *what() const throw();
};
#endif // !defined INC_structured_exception_
實現是:
#include
using namespace std;
#include "structured_exception.h"
#include "windows.h"
//
// ::
//
static void my_translator(unsigned code, EXCEPTION_POINTERS *info)
{
switch (code)
{
case EXCEPTION_ACCESS_VIOLATION:
throw access_violation(*info);
break;
case EXCEPTION_INT_DIVIDE_BY_ZERO:
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
throw divide_by_zero(*info);
break;
default:
throw structured_exception(*info);
break;
}
}
//
// structured_exception::
//
structured_exception::structured_exception
(EXCEPTION_POINTERS const &info) throw()
{
EXCEPTION_RECORD const &exception = *(info.ExceptionRecord);
address_ = exception.ExceptionAddress;
//code_ = exception.ExceptionCode;
}
void structured_exception::install() throw()
{
_set_se_translator(my_translator);
}
char const *structured_exception::what() const throw()
{
return "unspecified Structured Exception";
}
void const *structured_exception::where() const throw()
{
return address_;
}
//
// access_violation::
//
access_violation::access_violation
(EXCEPTION_POINTERS const &info) throw()
: structured_exception(info)
{
}
char const *access_violation::what() const throw()
{
return "access violation";
}
//
// divide_by_zero::
//
divide_by_zero::divide_by_zero
(EXCEPTION_POINTERS const &info) throw()
: structured_exception(info)
{
}
char const *divide_by_zero::what() const throw()
{
return "divide by zero";
}
注意:
l 那些本來在使用者的異常處理函式中的switch語句,現在移到了my_translator()中。不再是將所有SEH異常對映為單個值(如version 1中)或單個型別的物件(version 2),現在的my_translator()將它們對映為多個型別的物件(取決於執行時的實際環境)。
l structured_exception成為了一個基類。我沒有讓它成為純虛類,這是跟從了C++標準執行庫的引導(std::exception是個實體類)。
l 我沒有定義任何解構函式,因為編譯器隱含提供的的解構函式對這些簡單類足夠了。如果我定義了解構函式,它們將需要定義為virtual。
l what()現在返回了一個使用者友好的文字,取代了原來的SEH異常碼。
l 因為我不再測試和顯示這些程式碼,我去掉了資料成員code_。這使得structured_exception物件的大小減小了。(別太高興:節省的空間又被新增的vptr指標抵銷了,因為有了虛擬函式。)
l 因為模板方式更好,你應該放棄這種繼承的。我將它留給你作為習題。
試一下新的方案,將main檔案改為:
#include
using namespace std;
#include "structured_exception.h"
int main()
{
structured_exception::install();
//
// discriminate exception by dynamic type
//
try
{
*(int *) 0 = 0; // generate Structured Exception
}
catch (structured_exception const &exception)
{
cout << "caught " << exception.what() << endl;
}
//
// discriminate exception by static type
//
try
{
static volatile int i = 0;
i = 1 / i; // generate Structured Exception
}
catch (access_violation const &)
{
cout << "caught access violation" << endl;
}
catch (divide_by_zero const &)
{
cout << "caught divide by zero" << endl;
}
catch (structured_exception const &)
{
cout << "caught unspecified Structured Exception" << endl;
}
return 0;
}
再次執行,結果是:
caught access violation
caught divide by zero
1.4 Version 4:匹配於C++標準執行庫
我們所有的standard_exception繼承類都提供公有的成員
virtual char const *what() const;
來識別異常的動態型別。我不是隨便選取的函式名:所有的C++標準執行庫中的std::exception繼承類為同樣的目的提供了同樣的公有成員。並且,what()是每個繼承類的唯一的多型函式。
你可能已經注意到:
#include
class structured_exception : public std::exception
{
public:
structured_exception(EXCEPTION_POINTERS const &info) throw();
static void install() throw();
virtual char const *what() const throw();
void const *where() const throw();
private:
void const *address_;
};
因為structured_exception現在也是一個std:exception,我們可以用一個異常處理函式來同時捕獲這個異常族:
catch (std::exception const &exception)
並且用同樣的多型函式來獲取異常的型別:
catch (std::exception const &exception)
{
cout << "caught " << exception.what();
}
用這樣的方案,SEH異常能夠表現得與標準C++的固有行為一致。同時,我們仍然能夠特殊對待structured_exceptions並訪問它的特殊成員:
catch (structured_exception const &exception)
{
cout << "caught Structured Exception from " << exception.where();
}
當然,如果你想放棄沒有出現在std::exception繼承體系中的類成員,如where(),你完全可以不使用基類structured_exception,而是直接從std::exception繼承出access_violation等類。例如:一個divide-by-zero異常表示了一個程式值域控制錯誤,也就是說是個邏輯錯誤。你所以想直接從std::logic_error甚至是std::out_of_range派生devide_by_zero類。
我建議你看一下C++標準subclause 19.1 (“Exception classes”)以更好地理解C++標準執行庫的異常繼承體系,以及如何更好地將你的自定義異常熔入此繼承體系。
1.5 總結束
(略)來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1008592/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- C與C++中的異常處理 (轉)C++
- C與C++中的異常處理11 (轉)C++
- C與C++中的異常處理13 (轉)C++
- C與C++中的異常處理12 (轉)C++
- C與C++中的異常處理14 (轉)C++
- C與C++中的異常處理15 (轉)C++
- C與C++中的異常處理16 (轉)C++
- C與C++中的異常處理3 (轉)C++
- C與C++中的異常處理4 (轉)C++
- C與C++中的異常處理5 (轉)C++
- C與C++中的異常處理7 (轉)C++
- C與C++中的異常處理6 (轉)C++
- C與C++中的異常處理9 (轉)C++
- C與C++中的異常處理8 (轉)C++
- C與C++中的異常處理10 (轉)C++
- c++異常處理 (轉)C++
- C與C++中的異常處理2(part2) (轉)C++
- C與C++中的異常處理2(part1) (轉)C++
- C++ 異常處理C++
- C++異常處理C++
- C++異常處理與臨時副本C++
- c++異常處理格式C++
- c++ 異常處理(2)C++
- c++ 異常處理(1)C++
- windows核心程式設計---未處理異常,向量化異常處理與C++異常Windows程式設計C++
- 【C++】 C++異常捕捉和處理C++
- C++異常處理機制C++
- 17-異常處理
- 深入理解C++中的異常處理機制C++
- C++錯誤和異常處理C++
- C++整理19_異常處理C++
- C++ 異常處理機制詳解:輕鬆掌握異常處理技巧C++
- 【C++】 63_C語言異常處理C++C語言
- C++ 異常處理機制的實現C++
- 強制型別轉換時的異常處理_java與c++比較型別JavaC++
- Linux 下 C++ 異常處理技巧LinuxC++
- C++和結構化異常處理C++
- C/C++學習筆記八(斷言與異常處理)C++筆記