异常捕捉与处理
在软件开发的过程中错误捕捉显得尤为重要,因为有的错误会导致软件功能失常,而有的却会造成破坏性损失。世上没有不出错的软件。软件的逻辑错误,人为操作的失误,运行条件的改变等等因素都会导致异常的出现。下面的代码是一个例子:
char* pszData=NULL;//假设为全局变量
BOOL ReadData(void) { FILE* pFile=fopen("c:\\data.dat","r"); //假设 c:\data.dat文件长度为 1024 BYTE
if(pFile != NULL) { if(pszData) delete pszData; pszData=new char[1024]; if(1024 == fread(pszData,1024,1,pFile)) return TRUE; }
//打开文件失败错误,或文件长度不够
return FALSE; }
void PrintData() { for(int i=0;i<1024;i++) { printf("%x ",pszData[i]); } }
|
粗看这段代码应该是没有问题的,因为该段代码进行了错误处理,在操作没成功时返回了错误。但是在PrintData中就有一个隐患,如果pszData为NULL时怎么办,毫无疑问,此时会导致异常情况发生。也许在软件流程中如果ReadData返回错误后根本就无法进入PrintData,但是在一个十万行以上的程序中这种错误随时会存在。
另一个例子是关于内存分配的,如果你现在分配10K的内存出现失败,你的程序会如何反应,是退出还是继续。更令人沮丧的是很多开发人员在开发过程中对与某些可能出现的错误情况都未加以考虑,这使得出现错误时对错误的跟踪和定位成为极大的困难。
所以使用一种强制的机制保证一些致命错误能够被处理是一个明智的选择。比如说内存错,文件错等等。
在C++中引入了一种在C语言中不存在的特性,错误捕捉机制(try/catch),这是一种强制性的机制,如果程序中抛出的异常未被成功捕捉,该异常将一直会沿着函数调用的顺序上升,直到被捕捉到为止。而默认的main函数之外存在有异常捕捉代码,这段默认的异常捕捉代码将会终止程序并报告异常的发生。
下面我们先看看try/catch的语法的一个例子:
void do_something() { //循环产生各种异常
static int iTime=0; switch(iTime++%3) { case(0): throw (int)1; break; case(1): throw "error"; break; case(2): throw (double)1.1; break; } }
void CSam_sp_34Dlg::OnTc() { try { do_something(); } catch (int e) { AfxMessageBox("error handler1\n"); } catch (char* sz) { AfxMessageBox("error handler2\n"); } }
|
当你第三次执行OnTc时,由于产生的异常没有被成功捕捉所以将由默认的捕捉代码捕捉并终止程序。

这时候我们可以写另外一段代码来捕捉我们未能够估计到的异常。
void CSam_sp_34Dlg::OnTcE() { try { do_something(); } catch (int e) { AfxMessageBox("error handler1\n"); } catch (char* sz) { AfxMessageBox("error handler2\n"); } catch (...) { AfxMessageBox("catch all\n"); } }
|
catch(...)将会捕捉所有未指明类型的异常。在这里我们可以看到异常是可以分为很多类的,而分类的依据就是抛出异常时候所使用的数据类型。
在上面的例子中我们看到抛出异常的语法很简单,使用关键字throw就可以了,后面跟异常常的类型。如果单独使用throw则表示继续抛出当前异常,这种用法表明在处理当前异常后继续将该异常传递给其他的异常处理块进行处理。
void do_something_2() { try { do_something(); } catch(...) { AfxMessageBox("catched and throw"); throw;//继续传递该异常
} }
void CSam_sp_34Dlg::OnJt() { try { do_something_2(); } catch (int e) { AfxMessageBox("error handler1\n"); } catch (char* sz) { AfxMessageBox("error handler2\n"); } catch (...) { AfxMessageBox("catch all\n"); } }
|
最后我们来看看异常的处理顺序,异常首先会被距离try块最近的catch块捕捉到。看下面的例子:
void do_something_3() { try { do_something(); } catch(int e) { AfxMessageBox("catched int"); } }
void CSam_sp_34Dlg::OnCp() { try { do_something_3(); } catch (int e) {//这段代码是无意义的,因为do_something_3已经捕捉这种类型的异常
AfxMessageBox("error handler1\n"); } catch (char* sz) { AfxMessageBox("error handler2\n"); } catch (...) { AfxMessageBox("catch all\n"); } }
|
在MFC中定义了一些专门用于处理异常的类,所有这些类都由CException派生,并各自负责不同的异常情况,在MFC内部出现异常并抛出异常时将会抛出以下异常类:
类
| 用途
|
CException
| 异常基类
|
CNotSupportedException
| 进行系统不支持的操作时抛出的异常类
|
CMemoryException
| 内存分配失败时抛出的异常类
|
CArchiveException
| 文件串行化失败时抛出的异常类
|
CFileException
| 文件读写错误时抛出的异常类
|
CResourceException
| 资源无法装入时抛出的异常类
|
COleException
| OLE发生异常时抛出的异常类
|
CInternetException
| 使用WinInet功能时抛出的异常类
|
CUserException
| 用户定义的异常类 |
下面的代码演示了如何捕捉异常,我们需要注意到MFC抛出异常类的指针,但是我们不需要手工删除该指针,MFC在空闲时会自动删除,此外也不要调用delete对指针进行删除因为有些被抛出的异常指针可能是全局变量:
void CSam_sp_34Dlg::OnMfcF() { try { CFile fileTest("c:\\not_exist.txt",CFile::modeRead); } catch(CException *e) {//使用基类进行捕捉
e->ReportError(); }
try { CFile fileTest2("c:\\autoexec.bat",CFile::modeRead); char szLine[100]="rem test line\n"; fileTest2.Write(szLine,strlen(szLine)); } catch(CFileException *e) { //使用文件异常类进行捕捉
e->ReportError(); //不需要调用 e->Delete(); 进行删除
} }
|
MFC中的异常类提供了简便的获取错误信息的手段,但你捕捉到异常后可以通过检查异常类中的成员变量来检查错误原因。
掌握好异常处理对于编写无错代码帮助很大,你也应该在自己的代码中添加进异常抛出代码,这样可以提醒开发人员在开发过程加强对运行时错误的处理。下载本节代码
|