C++
语言异常处理
Visual C++ 提供了对 C 语言、 C++ 语言及 MFC 的支持,因而其涉及到的异常( exception )处理也包含了这三种类型,即 C 语言、 C++ 语言和 MFC 的异常处理。除此之外,微软对 C 和 C++ 的异常处理进行了扩展,提出了结构化异常处理( SEH )的概念,它支持 C 和 C++ (与之相比, MFC 异常处理仅支持 C++ )。
一个典型的异常处理包含如下几个步骤:
( 1 )程序执行时发生 错误 ;
( 2 )以一个异常对象(最简单的是一个整数)记录 错误 的原因及相关信息;
( 3 )程序检测到这个 错误 (读取异常对象);
( 4 )程序决定如何处理 错误 ;
( 5 )进行 错误 处理,并在此后恢复 / 终止程序的执行。
C 、 C++ 、 MFC 及 SEH 在这几个步骤中表现出了不同的特点。
C 语言异常处理
1 异常终止
标准 C 库提供了 abort() 和 exit() 两个函数,它们可以强行终止程序的运行,其声明处于 <stdlib.h> 头文件中。这两个函数本身不能检测异常,但在 C 程序发生异常后经常使用这两个函数进行程序终止。下面的这个例子描述了 exit() 的行为:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
exit(EXIT_SUCCESS);
printf(" 程序不会执行到这里 /n");
return 0;
}
在这个例子中, main 函数一开始就执行了 exit 函数(此函数原型为 void exit(int) ),因此,程序不会输出 " 程序不会执行到这里 " 。程序中的 exit(EXIT_SUCCESS) 表示程序正常结束,与之对应的 exit(EXIT_FAILURE) 表示程序执行 错误 ,只能强行终止。 EXIT_SUCCESS 、 EXIT_FAILURE 分别定义为 0 和 1 。
对于 exit 函数,我们可以利用 atexit 函数为 exit 事件 " 挂接 " 另外的函数,这种 " 挂接 " 有点类似 Windows 编程中的 " 钩子 " ( Hook )。譬如:
#include <stdio.h>
#include <stdlib.h>
static void atExitFunc(void)
{
printf("atexit 挂接的函数 /n");
}
int main(void)
{
atexit(atExitFunc);
exit(EXIT_SUCCESS);
printf(" 程序不会执行到这里 /n");
return 0;
}
程序输出 "atexit 挂接的函数 " 后即终止。来看下面的程序,我们不调用 exit 函数,看看 atexit 挂接的函数会否执行:
#include <stdio.h>
#include <stdlib.h>
static void atExitFunc(void)
{
printf("atexit 挂接的函数 /n");
}
int main(void)
{
atexit(atExitFunc);
//exit(EXIT_SUCCESS);
printf(" 不调用 exit 函数 /n");
return 0;
}
程序输出:
不调用 exit 函数
atexit 挂接的函数
这说明,即便是我们不调用 exit 函数,当程序本身退出时, atexit 挂接的函数仍然会被执行。
atexit 可以被多次执行,并挂接多个函数,这些函数的执行顺序为后挂接的先执行,例如:
#include <stdio.h>
#include <stdlib.h>
static void atExitFunc1(void)
{
printf("atexit 挂接的函数 1/n");
}
static void atExitFunc2(void)
{
printf("atexit 挂接的函数 2/n");
}
static void atExitFunc3(void)
{
printf("atexit 挂接的函数 3/n");
}
int main(void)
{
atexit(atExitFunc1);
atexit(atExitFunc2);
atexit(atExitFunc3);
return 0;
}
输出的结果是:
atexit 挂接的函数 3
atexit 挂接的函数 2
atexit 挂接的函数 1
在 Visual C++ 中,如果以 abort 函数(此函数不带参数,原型为 void abort(void) )终止程序,则会在 debug 模式运行时弹出对话框。
Visual C++ 提供了对 C 语言、 C++ 语言及 MFC 的支持,因而其涉及到的异常( exception )处理也包含了这三种类型,即 C 语言、 C++ 语言和 MFC 的异常处理。除此之外,微软对 C 和 C++ 的异常处理进行了扩展,提出了结构化异常处理( SEH )的概念,它支持 C 和 C++ (与之相比, MFC 异常处理仅支持 C++ )。
一个典型的异常处理包含如下几个步骤:
( 1 )程序执行时发生 错误 ;
( 2 )以一个异常对象(最简单的是一个整数)记录 错误 的原因及相关信息;
( 3 )程序检测到这个 错误 (读取异常对象);
( 4 )程序决定如何处理 错误 ;
( 5 )进行 错误 处理,并在此后恢复 / 终止程序的执行。
C 、 C++ 、 MFC 及 SEH 在这几个步骤中表现出了不同的特点。
C 语言异常处理
1 异常终止
标准 C 库提供了 abort() 和 exit() 两个函数,它们可以强行终止程序的运行,其声明处于 <stdlib.h> 头文件中。这两个函数本身不能检测异常,但在 C 程序发生异常后经常使用这两个函数进行程序终止。下面的这个例子描述了 exit() 的行为:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
exit(EXIT_SUCCESS);
printf(" 程序不会执行到这里 /n");
return 0;
}
在这个例子中, main 函数一开始就执行了 exit 函数(此函数原型为 void exit(int) ),因此,程序不会输出 " 程序不会执行到这里 " 。程序中的 exit(EXIT_SUCCESS) 表示程序正常结束,与之对应的 exit(EXIT_FAILURE) 表示程序执行 错误 ,只能强行终止。 EXIT_SUCCESS 、 EXIT_FAILURE 分别定义为 0 和 1 。
对于 exit 函数,我们可以利用 atexit 函数为 exit 事件 " 挂接 " 另外的函数,这种 " 挂接 " 有点类似 Windows 编程中的 " 钩子 " ( Hook )。譬如:
#include <stdio.h>
#include <stdlib.h>
static void atExitFunc(void)
{
printf("atexit 挂接的函数 /n");
}
int main(void)
{
atexit(atExitFunc);
exit(EXIT_SUCCESS);
printf(" 程序不会执行到这里 /n");
return 0;
}
程序输出 "atexit 挂接的函数 " 后即终止。来看下面的程序,我们不调用 exit 函数,看看 atexit 挂接的函数会否执行:
#include <stdio.h>
#include <stdlib.h>
static void atExitFunc(void)
{
printf("atexit 挂接的函数 /n");
}
int main(void)
{
atexit(atExitFunc);
//exit(EXIT_SUCCESS);
printf(" 不调用 exit 函数 /n");
return 0;
}
程序输出:
不调用 exit 函数
atexit 挂接的函数
这说明,即便是我们不调用 exit 函数,当程序本身退出时, atexit 挂接的函数仍然会被执行。
atexit 可以被多次执行,并挂接多个函数,这些函数的执行顺序为后挂接的先执行,例如:
#include <stdio.h>
#include <stdlib.h>
static void atExitFunc1(void)
{
printf("atexit 挂接的函数 1/n");
}
static void atExitFunc2(void)
{
printf("atexit 挂接的函数 2/n");
}
static void atExitFunc3(void)
{
printf("atexit 挂接的函数 3/n");
}
int main(void)
{
atexit(atExitFunc1);
atexit(atExitFunc2);
atexit(atExitFunc3);
return 0;
}
输出的结果是:
atexit 挂接的函数 3
atexit 挂接的函数 2
atexit 挂接的函数 1
在 Visual C++ 中,如果以 abort 函数(此函数不带参数,原型为 void abort(void) )终止程序,则会在 debug 模式运行时弹出对话框。
2
断言
(assert)
assert 宏在 C 语言程序的调试中发挥着重要的作用,它用于检测不会发生的情况,表明一旦发生了这样的情况,程序就实际上执行错误了,例如 strcpy 函数:
char *strcpy(char *strDest, const char *strSrc)
{
char * address = strDest;
assert((strDest != NULL) && (strSrc != NULL));
while ((*strDest++ = *strSrc++) != '/0')
;
return address;
}
其中包含断言 assert( (strDest != NULL) && (strSrc != NULL) ) ,它的意思是源和目的字符串的地址都不能为空,一旦为空,程序实际上就执行错误了,会引发一个 abort 。
assert 宏的定义为:
#ifdef NDEBUG
#define assert(exp) ((void)0)
#else
#ifdef __cplusplus
extern "C"
{
#endif
_CRTIMP void __cdecl _assert(void *, void *, unsigned);
#ifdef __cplusplus
}
#endif
#define assert(exp) (void)( (exp) || (_assert(#exp, __FILE__, __LINE__), 0) )
#endif /* NDEBUG */
如果程序不在 debug 模式下, assert 宏实际上什么都不做;而在 debug 模式下,实际上是对 _assert() 函数的调用,此函数将输出发生错误的文件名、代码行、条件表达式。例如下列程序:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
char * myStrcpy( char *strDest, const char *strSrc )
{
char *address = strDest;
assert( (strDest != NULL) && (strSrc != NULL) );
while( (*strDest++ = *strSrc++) != '/0' );
return address;
}
int main(void)
{
myStrcpy(NULL,NULL);
return 0;
}
在此程序中,为了避免我们的 strcpy 与 C 库中的 strcpy 重名,将其改为 myStrcpy 。
assert 宏在 C 语言程序的调试中发挥着重要的作用,它用于检测不会发生的情况,表明一旦发生了这样的情况,程序就实际上执行错误了,例如 strcpy 函数:
char *strcpy(char *strDest, const char *strSrc)
{
char * address = strDest;
assert((strDest != NULL) && (strSrc != NULL));
while ((*strDest++ = *strSrc++) != '/0')
;
return address;
}
其中包含断言 assert( (strDest != NULL) && (strSrc != NULL) ) ,它的意思是源和目的字符串的地址都不能为空,一旦为空,程序实际上就执行错误了,会引发一个 abort 。
assert 宏的定义为:
#ifdef NDEBUG
#define assert(exp) ((void)0)
#else
#ifdef __cplusplus
extern "C"
{
#endif
_CRTIMP void __cdecl _assert(void *, void *, unsigned);
#ifdef __cplusplus
}
#endif
#define assert(exp) (void)( (exp) || (_assert(#exp, __FILE__, __LINE__), 0) )
#endif /* NDEBUG */
如果程序不在 debug 模式下, assert 宏实际上什么都不做;而在 debug 模式下,实际上是对 _assert() 函数的调用,此函数将输出发生错误的文件名、代码行、条件表达式。例如下列程序:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
char * myStrcpy( char *strDest, const char *strSrc )
{
char *address = strDest;
assert( (strDest != NULL) && (strSrc != NULL) );
while( (*strDest++ = *strSrc++) != '/0' );
return address;
}
int main(void)
{
myStrcpy(NULL,NULL);
return 0;
}
在此程序中,为了避免我们的 strcpy 与 C 库中的 strcpy 重名,将其改为 myStrcpy 。
失败的断言也会弹出对话框,这是因为
_assert()
函数中也调用了
abort()
函数。
assert 本质上是一个宏,而不是一个函数,因而不能把带有副作用的表达式放入 assert 的 " 参数 " 中。
3 errno
errno 在 C 程序中是一个全局变量,这个变量由 C 运行时库函数设置,用户程序需要在程序发生异常时检测之。 C 运行库中主要在 math.h 和 stdio.h 头文件声明的函数中使用了 errno ,前者用于检测数学运算的合法性,后者用于检测 I/O 操作中(主要是文件)的错误,例如:
#include <errno.h>
#include <math.h>
#include <stdio.h>
int main(void)
{
errno = 0;
if (NULL == fopen("d://1.txt", "rb"))
{
printf("%d", errno);
}
else
{
printf("%d", errno);
}
return 0;
}
在此程序中,如果文件打开失败( fopen 返回 NULL ),证明发生了异常。我们读取 error 可以获知错误的原因,如果 D 盘根目录下不存在 "1.txt" 文件,将输出 2 ,表示文件不存在;在文件存在并正确打开的情况下,将执行到 else 语句,输出 0 ,证明 errno 没有被设置。
Visual C++ 提供了两种版本的 C 运行时库。 - 个版本供单线程应用程序调用,另一个版本供多线程应用程序调用。多线程运行时库与单线程运行时库的一个重大差别就是对于类似 errno 的全局变量,每个线程单独设置了一个。因此,对于多线程的程序,我们应该使用多线程 C 运行时库,才能获得正确的 error 值。
另外,在使用 errno 之前,我们最好将其设置为 0 ,即执行 errno = 0 的赋值语句。
4 其它
除了上述异常处理方式外,在 C 语言中还支持非局部跳转(使用 setjmp 和 longjmp )、信号(使用 signal 、 raise )、返回错误值或回传错误值给参数等方式进行一定能力的异常处理。
从以上分析可知, C 语言的异常处理是简单而不全面的。与 C++ 的异常处理比起来, C 语言异常处理相形见绌。
assert 本质上是一个宏,而不是一个函数,因而不能把带有副作用的表达式放入 assert 的 " 参数 " 中。
3 errno
errno 在 C 程序中是一个全局变量,这个变量由 C 运行时库函数设置,用户程序需要在程序发生异常时检测之。 C 运行库中主要在 math.h 和 stdio.h 头文件声明的函数中使用了 errno ,前者用于检测数学运算的合法性,后者用于检测 I/O 操作中(主要是文件)的错误,例如:
#include <errno.h>
#include <math.h>
#include <stdio.h>
int main(void)
{
errno = 0;
if (NULL == fopen("d://1.txt", "rb"))
{
printf("%d", errno);
}
else
{
printf("%d", errno);
}
return 0;
}
在此程序中,如果文件打开失败( fopen 返回 NULL ),证明发生了异常。我们读取 error 可以获知错误的原因,如果 D 盘根目录下不存在 "1.txt" 文件,将输出 2 ,表示文件不存在;在文件存在并正确打开的情况下,将执行到 else 语句,输出 0 ,证明 errno 没有被设置。
Visual C++ 提供了两种版本的 C 运行时库。 - 个版本供单线程应用程序调用,另一个版本供多线程应用程序调用。多线程运行时库与单线程运行时库的一个重大差别就是对于类似 errno 的全局变量,每个线程单独设置了一个。因此,对于多线程的程序,我们应该使用多线程 C 运行时库,才能获得正确的 error 值。
另外,在使用 errno 之前,我们最好将其设置为 0 ,即执行 errno = 0 的赋值语句。
4 其它
除了上述异常处理方式外,在 C 语言中还支持非局部跳转(使用 setjmp 和 longjmp )、信号(使用 signal 、 raise )、返回错误值或回传错误值给参数等方式进行一定能力的异常处理。
从以上分析可知, C 语言的异常处理是简单而不全面的。与 C++ 的异常处理比起来, C 语言异常处理相形见绌。
C++ 异常处理语法
标准 C++ 语言中专门集成了异常处理的相关语法(与之不同的是,所有的 C 标准库异常体系都需要运行库的支持,它不是语言内核支持的)。当然,异常处理被加到程序设计语言中,也是程序语言发展和逐步完善的必然结果。 C++ 不是唯一集成异常处理的语言。
1 C++ 的异常处理结构为:
try
{
// 可能引发异常的代码
}
catch(type_1 e)
{
// type_1 类型异常处理
}
catch(type_2 e)
{
// type_2 类型异常处理
}
catch (...)// 会捕获所有未被捕获的异常,必须最后出现
{
}
而异常的抛出方式为使用 throw(type e) , try 、 catch 和 throw 都是 C++ 为处理异常而添加的关键字。举例如下:
#include <stdio.h>
// 定义 Point 结构体(类)
typedef struct tagPoint
{
int x;
int y;
} Point;
// 扔出 int 异常的函数
static void f(int n)
{
throw 1;
}
// 扔出 Point 异常的函数
static void f(Point point)
{
Point p;
p.x = 0;
p.y = 0;
throw p;
}
int main()
{
Point point;
point.x = 0;
point.y = 0;
try
{
f(point); // 抛出 Point 异常
f(1); // 抛出 int 异常
}
catch (int e)
{
printf(" 捕获到 int 异常: %d/n", e);
}
catch (Point e)
{
printf(" 捕获到 Point 异常 :(%d,%d)/n", e.x, e.y);
}
return 0;
}
函数 f 定义了两个版本: f(int) 和 f(Point) ,分别抛出 int 和 Point 异常。当 main 函数的 try{ … } 中调用 f(point) 时和 f(1) 时,分别输出:
捕获到 Point 异常 :(0,0)
捕获到 int 异常: 1
在 C++ 中, throw 抛出异常的特点有:
( 1 )可以抛出基本数据类型异常,如 int 和 char 等;
( 2 )可以抛出复杂数据类型异常,如结构体(在 C++ 中结构体也是类)和类;
( 3 ) C++ 的异常处理必须由调用者主动检查。一旦抛出异常,而程序不捕获的话,那么 abort() 函数就会被调用,程序被终止;
( 4 )可以在函数头后加 throw([type-ID-list]) 给出异常规格,声明其能抛出什么类型的异常。 type-ID-list 是一个可选项,其中包括了一个或多个类型的名字,它们之间以逗号分隔。如果函数没有异常规格指定,则可以抛出任意类型的异常。
2 标准异常
下面给出了 C++ 提供的一些标准异常:
namespace std
{
//exception 派生
class logic_error; // 逻辑错误 , 在程序运行前可以检测出来
//logic_error 派生
class domain_error; // 违反了前置条件
class invalid_argument; // 指出函数的一个无效参数
class length_error; // 指出有一个超过类型 size_t 的最大可表现值长度的对象的企图
class out_of_range; // 参数越界
class bad_cast; // 在运行时类型识别中有一个无效的 dynamic_cast 表达式
class bad_typeid; // 报告在表达试 typeid(*p) 中有一个空指针 p
//exception 派生
class runtime_error; // 运行时错误 , 仅在程序运行中检测到
//runtime_error 派生
class range_error; // 违反后置条件
class overflow_error; // 报告一个算术溢出
class bad_alloc; // 存储分配错误
}
请注意观察上述类的层次结构,可以看出,标准异常都派生自一个公共的基类 exception 。基类包含必要的多态性函数提供异常描述,可以被重载。下面是 exception 类的原型:
class exception
{
public:
exception() throw();
exception(const exception& rhs) throw();
exception& operator=(const exception& rhs) throw();
virtual ~exception() throw();
virtual const char *wh
}
3
异常处理函数
在标准 C++ 中,还定义了数个异常处理的相关函数和类型(包含在头文件 <exception> 中):
namespace std
{
//EH 类型
class bad_exception;
class exception;
typedef void (*terminate_handler)();
typedef void (*unexpected_handler)();
// 函数
terminate_handler set_terminate(terminate_handler) throw();
unexpected_handler set_unexpected(unexpected_handler) throw();
void terminate();
void unexpected();
bool uncaught_exception();
}
其中的 terminate 相关函数与未被捕获的异常有关,如果一种异常没有被指定 catch 模块,则将导致 terminate() 函数被调用, terminate() 函数中会调用 ahort() 函数来终止程序。可以通过 set_terminate(terminate_handler) 函数为 terminate() 专门指定要调用的函数,例如:
#include <cstdio>
#include <exception>
using namespace std;
// 定义 Point 结构体(类)
typedef struct tagPoint
{
int x;
int y;
} Point;
// 扔出 Point 异常的函数
static void f()
{
Point p;
p.x = 0;
p.y = 0;
throw p;
}
//set_terminate 将指定的函数
void terminateFunc()
{
printf("set_terminate 指定的函数 /n");
}
int main()
{
set_terminate(terminateFunc);
try
{
f(); // 抛出 Point 异常
}
catch (int) // 捕获 int 异常
{
printf(" 捕获到 int 异常 ");
}
//Point 将不能被捕获到 , 引发 terminateFunc 函数被执行
return 0;
}
这个程序将在控制台上输出 "set_terminate 指定的函数 " 字符串,因为 Point 类型的异常没有被捕获到。当然,它也会弹出图 1 所示对话框(因为调用了 abort() 函数)。
上述给出的仅仅是一个 set_terminate 指定函数的例子。在实际工程中,往往使用 set_terminate 指定的函数进行一些清除性的工作,其后再调用 exit(int) 函数终止程序。这样, abort() 函数就不会被调用了,也不会输出对话框。
MFC 异常处理
MFC 中异常处理的语法和语义构建在标准 C++ 异常处理语法和语义的基础之上,其解决方案为:
MFC 异常处理 = MFC 异常处理类 + 宏
1 宏
MFC 定义了 TRY 、 CATCH (及 AND_CATCH 、 END_CATCH )和 THROW (及 THROW_LAST )等用于异常处理的宏,其本质上也是标准 C++ 的 try 、 catch 和 throw 的进一步强化,由这些宏的定义可知:
#ifndef _AFX_OLD_EXCEPTIONS
#define TRY { AFX_EXCEPTION_LINK _afxExceptionLink; try {
#define CATCH(class, e) } catch (class* e) /
{ ASSERT(e->IsKindOf(RUNTIME_CLASS(class))); /
_afxExceptionLink.m_pException = e;
#define AND_CATCH(class, e) } catch (class* e) /
{ ASSERT(e->IsKindOf(RUNTIME_CLASS(class))); /
_afxExceptionLink.m_pException = e;
#define END_CATCH } }
#define THROW(e) throw e
#define THROW_LAST() (AfxThrowLastCleanup(), throw)
// Advanced macros for smaller code
#define CATCH_ALL(e) } catch (CException* e) /
{ { ASSERT(e->IsKindOf(RUNTIME_CLASS(CException))); /
_afxExceptionLink.m_pException = e;
在标准 C++ 中,还定义了数个异常处理的相关函数和类型(包含在头文件 <exception> 中):
namespace std
{
//EH 类型
class bad_exception;
class exception;
typedef void (*terminate_handler)();
typedef void (*unexpected_handler)();
// 函数
terminate_handler set_terminate(terminate_handler) throw();
unexpected_handler set_unexpected(unexpected_handler) throw();
void terminate();
void unexpected();
bool uncaught_exception();
}
其中的 terminate 相关函数与未被捕获的异常有关,如果一种异常没有被指定 catch 模块,则将导致 terminate() 函数被调用, terminate() 函数中会调用 ahort() 函数来终止程序。可以通过 set_terminate(terminate_handler) 函数为 terminate() 专门指定要调用的函数,例如:
#include <cstdio>
#include <exception>
using namespace std;
// 定义 Point 结构体(类)
typedef struct tagPoint
{
int x;
int y;
} Point;
// 扔出 Point 异常的函数
static void f()
{
Point p;
p.x = 0;
p.y = 0;
throw p;
}
//set_terminate 将指定的函数
void terminateFunc()
{
printf("set_terminate 指定的函数 /n");
}
int main()
{
set_terminate(terminateFunc);
try
{
f(); // 抛出 Point 异常
}
catch (int) // 捕获 int 异常
{
printf(" 捕获到 int 异常 ");
}
//Point 将不能被捕获到 , 引发 terminateFunc 函数被执行
return 0;
}
这个程序将在控制台上输出 "set_terminate 指定的函数 " 字符串,因为 Point 类型的异常没有被捕获到。当然,它也会弹出图 1 所示对话框(因为调用了 abort() 函数)。
上述给出的仅仅是一个 set_terminate 指定函数的例子。在实际工程中,往往使用 set_terminate 指定的函数进行一些清除性的工作,其后再调用 exit(int) 函数终止程序。这样, abort() 函数就不会被调用了,也不会输出对话框。
MFC 异常处理
MFC 中异常处理的语法和语义构建在标准 C++ 异常处理语法和语义的基础之上,其解决方案为:
MFC 异常处理 = MFC 异常处理类 + 宏
1 宏
MFC 定义了 TRY 、 CATCH (及 AND_CATCH 、 END_CATCH )和 THROW (及 THROW_LAST )等用于异常处理的宏,其本质上也是标准 C++ 的 try 、 catch 和 throw 的进一步强化,由这些宏的定义可知:
#ifndef _AFX_OLD_EXCEPTIONS
#define TRY { AFX_EXCEPTION_LINK _afxExceptionLink; try {
#define CATCH(class, e) } catch (class* e) /
{ ASSERT(e->IsKindOf(RUNTIME_CLASS(class))); /
_afxExceptionLink.m_pException = e;
#define AND_CATCH(class, e) } catch (class* e) /
{ ASSERT(e->IsKindOf(RUNTIME_CLASS(class))); /
_afxExceptionLink.m_pException = e;
#define END_CATCH } }
#define THROW(e) throw e
#define THROW_LAST() (AfxThrowLastCleanup(), throw)
// Advanced macros for smaller code
#define CATCH_ALL(e) } catch (CException* e) /
{ { ASSERT(e->IsKindOf(RUNTIME_CLASS(CException))); /
_afxExceptionLink.m_pException = e;
#define AND_CATCH_ALL(e) } catch (CException* e) /
{ { ASSERT(e->IsKindOf(RUNTIME_CLASS(CException))); /
_afxExceptionLink.m_pException = e;
#define END_CATCH_ALL } } }
#define END_TRY } catch (CException* e) /
{ ASSERT(e->IsKindOf(RUNTIME_CLASS(CException))); /
_afxExceptionLink.m_pException = e; } }
这些宏在使用语法上,有如下特点:
( 1 )用 TRY 块包含可能产生异常的代码;
( 2 )用 CATCH 块检测并处理异常。要注意的是, CATCH 块捕获到的不是异常对象,而是指向异常对象的指针。此外, MFC 靠动态类型来辨别异常对象;
( 3 )可以在一个 TRY 块上捆绑多个异常处理捕获块,第一次捕获使用宏 CATCH ,以后的使用 AND_CATCH ,而 END_CATCH 则用来结束异常捕获队列;
( 4 )在异常处理程序内部,可以用 THROW_LAST 再次抛出最近一次捕获的异常。
2 MFC 异常处理类
MFC 较好地将异常封装到 CException 类及其派生类中,自成体系,下表给出了 MFC 提供的预定义异常:
异常类 含义
CMemoryException 内存不足
CFileException 文件异常
CArchiveException 存档 / 序列化异常
CNotSupportedException 响应对不支持服务的请求
CResourceException Windows 资源分配异常
CDaoException 数据库异常( DAO 类)
CDBException 数据库异常( ODBC 类)
COleException OLE 异常
COleDispatchException 调度(自动化)异常
CUserException 用消息框警告用户然后引发一般 CException 的异常
标准 C++ 的异常处理可以处理任意类型的异常,而上节的 MFC 宏则只能处理 CException 的派生类型,下面我们看一个 CFileException 的使用例子:
#include <iostream.h>
#include "afxwin.h"
int main()
{
TRY
{
CFile f( "d://1.txt", CFile::modeWrite );
}
CATCH( CFileException, e )
{
if( e->m_cause == CFileException::fileNotFound )
cout << "ERROR: File not found/n" << endl;
}
}
要想这个程序能正确地执行,我们可以在第一个
__try
块的外面再套一个
__try
块和一个接收
filter-expression
返回值为
EXCEPTION_EXECUTE_HANDLER
的
__except
块,程序改为:
#include "stdio.h"
void main()
{
int* p = NULL; // 定义一个空指针
puts("SEH begin");
__try
{
__try
{
puts("in try");
__try
{
puts("in try");
*p = 0; // 引发一个内存访问异常
}
__finally
{
puts("in finally");
}
}
__except(puts("in filter"), 0)
{
puts("in except");
}
}
__except(puts("in filter"), 1)
{
puts("in except");
}
puts("SEH end");
}
程序输出:
#include "stdio.h"
void main()
{
int* p = NULL; // 定义一个空指针
puts("SEH begin");
__try
{
__try
{
puts("in try");
__try
{
puts("in try");
*p = 0; // 引发一个内存访问异常
}
__finally
{
puts("in finally");
}
}
__except(puts("in filter"), 0)
{
puts("in except");
}
}
__except(puts("in filter"), 1)
{
puts("in except");
}
puts("SEH end");
}
程序输出:
SEH begin
in try //
执行
__try
块
in try // 执行嵌入的 __try 块
in filter1 // 执行 filter-expression ,返回 EXCEPTION_CONTINUE_SEARCH
in filter2 // 执行 filter-expression ,返回 EXCEPTION_EXECUTE_HANDLER
in finally // 展开嵌入的 __finally
in except2 // 执行对应的 __except 块
SEH end // 处理完毕
由此可以看出,因为第一个 __except 的 filter-expression 返回 EXCEPTION_CONTINUE_SEARCH 的原因, "in except1" 没有被输出。程序之所以没有崩溃,是因为最终碰到了接收 EXCEPTION_EXECUTE_HANDLER 的第 2 个 __except 。
SEH 使用复杂的地方在于较难控制异常处理的流动方向,弄不好程序就 " 挂 " 了。如果把例 4-1 中的 __except(puts("in filter"), 1) 改为 __except(puts("in filter"), -1) ,程序会进入一个死循环,输出:
SEH begin
in try // 执行 __try 块
in try // 执行嵌入的 __try 块
in filter // 执行 filter-expression ,返回 EXCEPTION_CONTINUE_EXECUTION
in filter
in filter
in filter
in filter
… // 疯狂输出 "in filter"
最后疯狂地输出 "in filter" ,我们把断点设置在 __except(puts("in filter"), -1) 语句之前,按 F5 会不断进入此断点。
in try // 执行嵌入的 __try 块
in filter1 // 执行 filter-expression ,返回 EXCEPTION_CONTINUE_SEARCH
in filter2 // 执行 filter-expression ,返回 EXCEPTION_EXECUTE_HANDLER
in finally // 展开嵌入的 __finally
in except2 // 执行对应的 __except 块
SEH end // 处理完毕
由此可以看出,因为第一个 __except 的 filter-expression 返回 EXCEPTION_CONTINUE_SEARCH 的原因, "in except1" 没有被输出。程序之所以没有崩溃,是因为最终碰到了接收 EXCEPTION_EXECUTE_HANDLER 的第 2 个 __except 。
SEH 使用复杂的地方在于较难控制异常处理的流动方向,弄不好程序就 " 挂 " 了。如果把例 4-1 中的 __except(puts("in filter"), 1) 改为 __except(puts("in filter"), -1) ,程序会进入一个死循环,输出:
SEH begin
in try // 执行 __try 块
in try // 执行嵌入的 __try 块
in filter // 执行 filter-expression ,返回 EXCEPTION_CONTINUE_EXECUTION
in filter
in filter
in filter
in filter
… // 疯狂输出 "in filter"
最后疯狂地输出 "in filter" ,我们把断点设置在 __except(puts("in filter"), -1) 语句之前,按 F5 会不断进入此断点。
各种异常处理的对比
下表给出了从各个方面对这本文所给出的 Visual C++ 所支持的四种异常处理进行的对比:
异常处理 支持语言 是否标准 复杂度 推荐使用
C 异常处理 C 语言 标准 C 简单 推荐
C++ 异常处理 C++ 语言 标准 C++ 较简单 推荐
MFC 异常处理 C++ 语言 仅针对 MFC 程序 较简单 不推荐
SEH 异常处理 C 和 C++ 语言 仅针对 Microsoft 编译环境 较复杂 不推荐
下表给出了从各个方面对这本文所给出的 Visual C++ 所支持的四种异常处理进行的对比:
异常处理 支持语言 是否标准 复杂度 推荐使用
C 异常处理 C 语言 标准 C 简单 推荐
C++ 异常处理 C++ 语言 标准 C++ 较简单 推荐
MFC 异常处理 C++ 语言 仅针对 MFC 程序 较简单 不推荐
SEH 异常处理 C 和 C++ 语言 仅针对 Microsoft 编译环境 较复杂 不推荐
387

被折叠的 条评论
为什么被折叠?



