Visual C++中的异常处理浅析(下)

简介:


2.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()函数就不会被调用了,也不会输出图1所示对话框。
 
关于标准C++的异常处理,还包含一些比较复杂的技巧和内容,我们可以查阅《more effective C++》的条款9~条款15
 
3.MFC 异常处理
MFC 中异常处理的语法和语义构建在标准C++异常处理语法和语义的基础之上,其解决方案为:
MFC 异常处理 = MFC 异常处理类 + 宏
 
3.1
MFC 定义了TRYCATCH(及AND_CATCHEND_CATCH)和THROW(及THROW_LAST)等用于异常处理的宏,其本质上也是标准C++trycatchthrow的进一步强化,由这些宏的定义可知:
#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 再次抛出最近一次捕获的异常。
 
3.2 MFC  异常处理类
MFC 较好地将异常封装到CException类及其派生类中,自成体系,下表给出了MFC 提供的预定义异常:
异常类
含义
CMemoryException
内存不足
CFileException
文件异常
CArchiveException
存档/序列化异常
CNotSupportedException
响应对不支持服务的请求
CResourceException
Windows  资源分配异常
CDaoException
数据库异常(DAO 类)
CDBException
数据库异常(ODBC 类)
COleException
OLE  异常
COleDispatchException
调度(自动化)异常
CUserException
用消息框警告用户然后引发一般 CException的异常
 
标准C++的异常处理可以处理任意类型的异常,而3.1节的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;      
    }
    END_CATCH    
}
在这个程序中,如果D盘根目录下不存在“1.TXT”这个文件,将抛出CFileException异常,而且错误原因成员变量m_cause被设置为fileNotFound,我们以CATCH( CFileException, e )就可以捕获到。错误原因被定义为CFileException中的枚举(enum),如下:
enum {
    none,
    generic,
    fileNotFound,
    badPath,
    tooManyOpenFiles,
    accessDenied,
    invalidFile,
    removeCurrentDir,
    directoryFull,
    badSeek,
    hardIO,
    sharingViolation,
    lockViolation,
    diskFull,
    endOfFile
};
我们在使用MFC相关类时,MFC会自动抛出异常,当然我们也可以自行在程序中利用 AfxThrowXXXException() 抛出各种类型的异常,其中的XXX与前文的MFC异常类表对应。我们看AfxThrowFileException的例子:
#include <iostream.h>
#include "afxwin.h"
 
int main()
{
    TRY
    {
       AfxThrowFileException(CFileException::fileNotFound);
    }
    CATCH( CFileException, e )
    {
       if( e->m_cause == CFileException::fileNotFound )
           cout << "ERROR: File not found\n" << endl;      
    }
    END_CATCH    
}
在此程序中,我们在TRY块自行利用MFC提供的全局函数AfxThrowFileException抛出了CFileException异常,其后在CATCH块抓住了这个异常。
 
MFC 建议不再使用TRYCATCHTHROW宏,而是直接使用标准C++的方式。
4. 结构化异常处理
结构化异常处理(Structured Exception Handling,简称SEH)是微软针对Windows程序异常处理进行的扩展,在Visual C++中,它同时支持CC++语言。SEH不宜与标准C++异常处理和MFC异常处理混用,对于C++程序,微软建议使用标准C++的异常处理。
为了支持SEHVisual C++中定义了四个关键字(由于这些关键字是非标准关键字,其它编译器不一定支持),用以扩展C++语言:
1__except
2__finally
3__leave
4__try
其基本语法为:
__try
{
...// 可能导致异常的被监控代码块
}
__except(filter-expression)
{
...// 异常处理函数
}
或:
__try
{
...
}
__finally
{
...// 终止
}
其执行的步骤如下:
1__try块被执行;
2)如果__try块没有出现异常,则执行到__except块之后;否则,执行到__except块,根据filter-expression的值决定异常处理方法:
a. filter-expression 的值为EXCEPTION_CONTINUE_EXECUTION (–1)
恢复异常,从发生异常处下面开始执行,异常处理函数本身不被执行;
b. filter-expression 的值为EXCEPTION_CONTINUE_SEARCH (0)
异常不被识别,拒绝捕获异常,继续搜索下一个异常处理函数;
c. filter-expression 的值为EXCEPTION_EXECUTE_HANDLER (1)
异常被识别,终止异常,从异常发生处开始退栈,一路上遇到的终止函数都被执行。
看看这个例子:
// 4-1
#include "stdio.h"
 
void main()
{
    int* p = NULL;   //  定义一个空指针
    puts("SEH begin");
    __try
    {
       puts("in try");
       __try
       {
           puts("in try");
           *p = 0;    //  引发一个内存访问异常
       }
       __finally
       {
           puts("in finally");
       }
    }
    __except(puts("in filter"), 1)
    {
       puts("in except");
    }
    puts("SEH end");
}
程序的输出为:
SEH begin
in try      // 执行__try
in try      // 执行嵌入的__try
in filter   // 执行filter-expression,返回EXCEPTION_EXECUTE_HANDLER
in finally  // 展开嵌入的__finally
in except   // 执行对应的__except
SEH end     // 处理完毕
如果我们把__except(puts("in filter"), 1)改为__except(puts("in filter"), 0),程序的输出将变为:
SEH begin
in try      // 执行__try
in try      // 执行嵌入的__try
in filter   // 执行filter-expression,返回EXCEPTION_CONTINUE_SEARCH
in finally  // 展开嵌入的__finally
程序的执行也告崩溃,弹出如图3所示的对话框。
不能被正确执行的SEH
要想这个程序能正确地执行,我们可以在第一个__try块的外面再套一个__try块和一个接收filter-expression返回值为EXCEPTION_EXECUTE_HANDLER__except块,程序改为:
// 4-2
#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     // 处理完毕
由此可以看出,因为第一个__exceptfilter-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会不断进入此断点。
 
5. 各种异常处理的对比
     下表给出了从各个方面对这本文所给出的Visual C++所支持的四种异常处理进行的对比:
异常处理
支持语言
是否标准
复杂度
推荐使用
C 异常处理
C 语言
标准C
简单
推荐
C++ 异常处理
C++ 语言
标准C++
较简单
推荐
MFC 异常处理
C++ 语言
仅针对MFC程序
较简单
不推荐
SEH 异常处理
C C++语言
仅针对Microsoft
编译环境
较复杂
不推荐
   
     本文所讲解的仅仅是Visual C++异常处理的初步知识,对于更深入的内容,还需要我们在不断的编程过程中去领悟和学习。
在程序设计过程中,我们不能嫌异常处理“麻烦”,对可能的错误视而不见、不加考虑。因为避免了异常处理的“麻烦”,将会给我们的程序带来更大的“麻烦”。而程序中包含必要的异常处理,也是对一位优秀程序员的基本要求。




 本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/120816,如需转载请自行联系原作者


相关文章
|
29天前
|
Windows
Microsoft Visual C++2015-2019 安装失败 0x80240017
Microsoft Visual C++2015-2019 安装失败 0x80240017
36 0
|
1月前
|
JSON C++ 数据格式
【C++】Visual Studio C++使用配置Json库文件(老爷式教学)
【C++】Visual Studio C++使用配置Json库文件(老爷式教学)
|
1月前
|
关系型数据库 数据库 C++
【C++】Windows使用Visual Studio C++链接云数据库PostgreSQL(沉浸式老爷教学)
【C++】Windows使用Visual Studio C++链接云数据库PostgreSQL(沉浸式老爷教学)
|
21小时前
|
NoSQL 测试技术 C++
C++的异常处理及错误调试技巧
C++的异常处理及错误调试技巧
5 0
|
10天前
|
存储 C++
C++ 异常处理机制详解:轻松掌握异常处理技巧
C++ 异常处理提供结构化错误管理,增强程序健壮性。通过`throw`抛出异常,`try-catch`捕获并处理。示例展示了当年龄小于18时抛出异常。优点包括提高健壮性和代码可维护性,但可能降低性能并复杂化代码。另外,介绍了四种在C++中相加两个数的方法,包括使用运算符、函数、类、STL函数和lambda表达式。
18 0
|
15天前
|
安全 编译器 C++
C++从入门到精通:3.2异常处理——掌握C++的异常处理机制,提高程序健壮性
C++从入门到精通:3.2异常处理——掌握C++的异常处理机制,提高程序健壮性
|
15天前
|
程序员 编译器 C语言
【C++高阶(七)】C++异常处理的方式
【C++高阶(七)】C++异常处理的方式
|
1月前
|
C++
C++语言异常处理学习应用案例
C++异常处理保证程序在运行时遇到错误(如除数为0)时不崩溃。以下是一个示例:程序接收用户输入的两个整数并进行除法运算。若除数为0,则抛出`std::runtime_error`异常。`try-catch`结构用来捕获并处理异常,当出现异常时,输出错误信息,使程序能继续执行。
15 4
|
1月前
|
存储 C++
【C++】Visual Studio C++ 配置并使用gtest(不好用你捶我)
【C++】Visual Studio C++ 配置并使用gtest(不好用你捶我)
|
1月前
|
C++
C++异常处理try和throw以及catch的使用
C++异常处理try和throw以及catch的使用