Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3534512
  • 博文数量: 864
  • 博客积分: 14125
  • 博客等级: 上将
  • 技术积分: 10634
  • 用 户 组: 普通用户
  • 注册时间: 2007-07-27 16:53
个人简介

https://github.com/zytc2009/BigTeam_learning

文章分类

全部博文(864)

文章存档

2023年(1)

2021年(1)

2019年(3)

2018年(1)

2017年(10)

2015年(3)

2014年(8)

2013年(3)

2012年(69)

2011年(103)

2010年(357)

2009年(283)

2008年(22)

分类: C/C++

2010-05-05 19:00:57

第3集 C++中catch(…)如何使用
--------------------------------------------------------------------------------

CSAI.CN原创 王胜祥 2005年05月16日
  上一篇文章中详细讲了讲C++异常处理模型的trycatch使用语法,其中catch关键字是用来定义catch block的,它后面带一个参数,用来与异常对象的数据类型进行匹配。注意catch关键字只能定义一个参数,因此每个catch block只能是一种数据类型的异常对象的错误处理模块。如果要想使一个catch block能抓获多种数据类型的异常对象的话,怎么办?C++标准中定义了一种特殊的catch用法,那就是” catch(…)”。
感性认识

  1、catch(…)到底是一个什么样的东东,先来个感性认识吧!看例子先:

int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是int,值为1)
throw 1;
}
//catch( int& value )
//注意这里catch语句
catch( …)
{
cout << "在 catch(…) block 中, 抛出的int类型的异常对象被处理" << endl;
}
}

  2、哈哈!int类型的异常被catch(…)抓获了,再来另一个例子:

int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是double,值为0.5)
throw 0.5;
}
//catch( double& value )
//注意这里catch语句
catch( …)
{
cout << "在 catch(…) block 中, double类型的异常对象也被处理" << endl;
}
}

  3、同样,double类型的异常对象也被catch(…)块抓获了。是的,catch(..)能匹配成功所有的数据类型的异常对象,包括C++语言提供所有的原生数据类型的异常对象,如int、double,还有char*、int*这样的指针类型,另外还有数组类型的异常对象。同时也包括所有自定义的抽象数据类型。例程如下:

int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是char*)
char* p=0;
throw p;
}
//catch( char* value )
//注意这里catch语句
catch( …)
{
cout << "在 catch(…) block 中, char*类型的异常对象也被处理" << endl;
}
}


int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是int[])
int a[4];
throw a;
}
//catch( int value[] )
//注意这里catch语句
catch( …)
{
cout << "在 catch(…) block 中, int[]类型的异常对象也被处理" << endl;
}
}

  4、对于抽象数据类型的异常对象。catch(…)同样有效,例程如下:

class MyException
{
public:
protected:
int code;
};

int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是MyException)
throw MyException();
}
//catch(MyException& value )
//注意这里catch语句
catch( …)
{
cout << "在catch(…) block中, MyException类型的异常对象被处理" << endl;
}
}
对catch(…)有点迷糊?
1、究竟对catch(…)有什么迷糊呢?还是看例子先吧!
void main()
{
int* p = 0;

try
{
// 注意:下面这条语句虽然不是throw语句,但它在执行时会导致系统
// 出现一个存储保护错误的异常(access violation exception)
*p = 13; // causes an access violation exception;
}
catch(...)
{
//catch(…)能抓获住上面的access violation exception异常吗?
cout << "在catch(…) block中" << endl;
}
}

  请问上面的程序运行时会出现什么结果吗?catch(…)能抓获住系统中出现的access violation exception异常吗?朋友们!和我们的主人公阿愚一样,自己动手去测试一把!
结果又如何呢?实际上它有两种不同的运行结果,在window2000系统下用VC来测试运行这个小程序时,发现程序能输出"在catch(…) block中"的语句在屏幕上,也即catch(…) 能成功抓获住系统中出现的access violation exception异常,很厉害吧!但如果这个同样的程序在linux下用gcc编译后运行时,程序将会出现崩溃,并在屏幕上输出”segment fault”的错误信息。

  主人公阿愚有点急了,也开始有点迷糊了,为什么?为什么?为什么同样一个程序在两种不同的系统上有不同的表现呢?其原因就是:对于这种由于硬件或操作系统出现的系统异常(例如说被零除、内存存储控制异常、页错误等等)时,window2000系统有一个叫做结构化异常处理(Structured Exception Handling,SEH)的机制,这个东东太厉害了,它能和VC中的C++异常处理模型很好的结合上(实际上VC实现的C++异常处理模型很大程度上建立在SEH机制之上的,或者说它是SEH的扩展,后面文章中会详细阐述并分析这个久富盛名的SEH,看看catch(…)是如何神奇接管住这种系统异常出现后的程序控制流的,不过这都是后话)。而在linux系统下,系统异常是由信号处理编程方法来控制的(信号处理编程,signal processing progamming。在介绍unix和linux下如何编程的书籍中,都会有对信号处理编程详细的介绍,当然执著的主人公阿愚肯定对它也不会放过,会深入到unix沿袭下来的信号处理编程内部的实现机制,并尝试完善改进它,使它也能够较好地和C++异常处理模型结合上)。

  那么C++标准中对于这种同一个程序有不同的运行结果有何解释呢?这里需要注意的是,window2000系统下catch(…)能捕获住系统异常,这完全是它自己的扩展。在C++标准中并没有要求到这一点,它只规定catch(…)必须能捕获程序中所有通过throw语句抛出的异常。因此上面的这个程序在linux系统下的运行结果也完全是符合C++标准的。虽然大家也必须承认window2000系统下对C++异常处理模型的这种扩展确实是一个很不错的完善,极大得提高了程序的安全性。

为什么要用catch(…)这个东东?

  程序员朋友们也许会说,这还有问吗?这篇文章的一开始不就讲到了吗?catch(…)能够捕获多种数据类型的异常对象,所以它提供给程序员一种对异常对象更好的控制手段,使开发的软件系统有很好的可靠性。因此一个比较有经验的程序员通常会这样组织编写它的代码模块,如下:

void Func()
{
try
{
// 这里的程序代码完成真正复杂的计算工作,这些代码在执行过程中
// 有可能抛出DataType1、DataType2和DataType3类型的异常对象。
}
catch(DataType1& d1)
{
}
catch(DataType2& d2)
{
}
catch(DataType3& d3)
{
}
// 注意上面try block中可能抛出的DataType1、DataType2和DataType3三
// 种类型的异常对象在前面都已经有对应的catch block来处理。但为什么
// 还要在最后再定义一个catch(…) block呢?这就是为了有更好的安全性和
// 可靠性,避免上面的try block抛出了其它未考虑到的异常对象时导致的程
// 序出现意外崩溃的严重后果,而且这在用VC开发的系统上更特别有效,因
// 为catch(…)能捕获系统出现的异常,而系统异常往往令程序员头痛了,现
// 在系统一般都比较复杂,而且由很多人共同开发,一不小心就会导致一个
// 指针变量指向了其它非法区域,结果意外灾难不幸发生了。catch(…)为这种
// 潜在的隐患提供了一种有效的补救措施。
catch(…)
{
}
}

  还有,特别是VC程序员为了使开发的系统有更好的可靠性,往往在应用程序的入口函数中(如MFC框架的开发环境下CXXXApp::InitInstance())和工作线程的入口函数中加上一个顶层的trycatch块,并且使用catch(…)来捕获一切所有的异常,如下:

BOOL CXXXApp::InitInstance()
{
if (!AfxSocketInit())
{
AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
return FALSE;
}

AfxEnableControlContainer();

// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need.

#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif


// 注意这里有一个顶层的trycatch块,并且使用catch(…)来捕获一切所有的异常
try
{
CXXXDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
}
catch(…)
{
// dump出系统的一些重要信息,并通知管理员查找出现意外异常的原因。
// 同时想办法恢复系统,例如说重新启动应用程序等
}

// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
}

  通过上面的例程和分析可以得出,由于catch(…)能够捕获所有数据类型的异常对象,所以在恰当的地方使用catch(…)确实可以使软件系统有着更好的可靠性。这确实是大家使用catch(…)这个东东最好的理由。但不要误会的是,在C++异常处理模型中,不只有catch(…)方法能够捕获几乎所有类型的异常对象(也许有其它更好的方法,在下一篇文章中主人公阿愚带大家一同去探讨一下),可C++标准中为什么会想到定义这样一个catch(…)呢?有过java或C#编程开发经验的程序员会发现,在它们的异常处理模型中,并没有这样类似的一种语法,可这里不得不再次强调的是,java中的异常处理模型是C++中的异常处理模型的完善改进版,可它反而没有了catch(…),为何呢?还是先去看看下一章吧,“C++的异常处理和面向对象的紧密关系”。也许大家能找到一个似乎合理的原因。
第3集 C++中catch(…)如何使用
--------------------------------------------------------------------------------

CSAI.CN原创 王胜祥 2005年05月16日
  上一篇文章中详细讲了讲C++异常处理模型的trycatch使用语法,其中catch关键字是用来定义catch block的,它后面带一个参数,用来与异常对象的数据类型进行匹配。注意catch关键字只能定义一个参数,因此每个catch block只能是一种数据类型的异常对象的错误处理模块。如果要想使一个catch block能抓获多种数据类型的异常对象的话,怎么办?C++标准中定义了一种特殊的catch用法,那就是” catch(…)”。
感性认识

  1、catch(…)到底是一个什么样的东东,先来个感性认识吧!看例子先:

int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是int,值为1)
throw 1;
}
//catch( int& value )
//注意这里catch语句
catch( …)
{
cout << "在 catch(…) block 中, 抛出的int类型的异常对象被处理" << endl;
}
}

  2、哈哈!int类型的异常被catch(…)抓获了,再来另一个例子:

int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是double,值为0.5)
throw 0.5;
}
//catch( double& value )
//注意这里catch语句
catch( …)
{
cout << "在 catch(…) block 中, double类型的异常对象也被处理" << endl;
}
}

  3、同样,double类型的异常对象也被catch(…)块抓获了。是的,catch(..)能匹配成功所有的数据类型的异常对象,包括C++语言提供所有的原生数据类型的异常对象,如int、double,还有char*、int*这样的指针类型,另外还有数组类型的异常对象。同时也包括所有自定义的抽象数据类型。例程如下:

int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是char*)
char* p=0;
throw p;
}
//catch( char* value )
//注意这里catch语句
catch( …)
{
cout << "在 catch(…) block 中, char*类型的异常对象也被处理" << endl;
}
}


int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是int[])
int a[4];
throw a;
}
//catch( int value[] )
//注意这里catch语句
catch( …)
{
cout << "在 catch(…) block 中, int[]类型的异常对象也被处理" << endl;
}
}

  4、对于抽象数据类型的异常对象。catch(…)同样有效,例程如下:

class MyException
{
public:
protected:
int code;
};

int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是MyException)
throw MyException();
}
//catch(MyException& value )
//注意这里catch语句
catch( …)
{
cout << "在catch(…) block中, MyException类型的异常对象被处理" << endl;
}
}
对catch(…)有点迷糊?
1、究竟对catch(…)有什么迷糊呢?还是看例子先吧!
void main()
{
int* p = 0;

try
{
// 注意:下面这条语句虽然不是throw语句,但它在执行时会导致系统
// 出现一个存储保护错误的异常(access violation exception)
*p = 13; // causes an access violation exception;
}
catch(...)
{
//catch(…)能抓获住上面的access violation exception异常吗?
cout << "在catch(…) block中" << endl;
}
}

  请问上面的程序运行时会出现什么结果吗?catch(…)能抓获住系统中出现的access violation exception异常吗?朋友们!和我们的主人公阿愚一样,自己动手去测试一把!
结果又如何呢?实际上它有两种不同的运行结果,在window2000系统下用VC来测试运行这个小程序时,发现程序能输出"在catch(…) block中"的语句在屏幕上,也即catch(…) 能成功抓获住系统中出现的access violation exception异常,很厉害吧!但如果这个同样的程序在linux下用gcc编译后运行时,程序将会出现崩溃,并在屏幕上输出”segment fault”的错误信息。

  主人公阿愚有点急了,也开始有点迷糊了,为什么?为什么?为什么同样一个程序在两种不同的系统上有不同的表现呢?其原因就是:对于这种由于硬件或操作系统出现的系统异常(例如说被零除、内存存储控制异常、页错误等等)时,window2000系统有一个叫做结构化异常处理(Structured Exception Handling,SEH)的机制,这个东东太厉害了,它能和VC中的C++异常处理模型很好的结合上(实际上VC实现的C++异常处理模型很大程度上建立在SEH机制之上的,或者说它是SEH的扩展,后面文章中会详细阐述并分析这个久富盛名的SEH,看看catch(…)是如何神奇接管住这种系统异常出现后的程序控制流的,不过这都是后话)。而在linux系统下,系统异常是由信号处理编程方法来控制的(信号处理编程,signal processing progamming。在介绍unix和linux下如何编程的书籍中,都会有对信号处理编程详细的介绍,当然执著的主人公阿愚肯定对它也不会放过,会深入到unix沿袭下来的信号处理编程内部的实现机制,并尝试完善改进它,使它也能够较好地和C++异常处理模型结合上)。

  那么C++标准中对于这种同一个程序有不同的运行结果有何解释呢?这里需要注意的是,window2000系统下catch(…)能捕获住系统异常,这完全是它自己的扩展。在C++标准中并没有要求到这一点,它只规定catch(…)必须能捕获程序中所有通过throw语句抛出的异常。因此上面的这个程序在linux系统下的运行结果也完全是符合C++标准的。虽然大家也必须承认window2000系统下对C++异常处理模型的这种扩展确实是一个很不错的完善,极大得提高了程序的安全性。

为什么要用catch(…)这个东东?

  程序员朋友们也许会说,这还有问吗?这篇文章的一开始不就讲到了吗?catch(…)能够捕获多种数据类型的异常对象,所以它提供给程序员一种对异常对象更好的控制手段,使开发的软件系统有很好的可靠性。因此一个比较有经验的程序员通常会这样组织编写它的代码模块,如下:

void Func()
{
try
{
// 这里的程序代码完成真正复杂的计算工作,这些代码在执行过程中
// 有可能抛出DataType1、DataType2和DataType3类型的异常对象。
}
catch(DataType1& d1)
{
}
catch(DataType2& d2)
{
}
catch(DataType3& d3)
{
}
// 注意上面try block中可能抛出的DataType1、DataType2和DataType3三
// 种类型的异常对象在前面都已经有对应的catch block来处理。但为什么
// 还要在最后再定义一个catch(…) block呢?这就是为了有更好的安全性和
// 可靠性,避免上面的try block抛出了其它未考虑到的异常对象时导致的程
// 序出现意外崩溃的严重后果,而且这在用VC开发的系统上更特别有效,因
// 为catch(…)能捕获系统出现的异常,而系统异常往往令程序员头痛了,现
// 在系统一般都比较复杂,而且由很多人共同开发,一不小心就会导致一个
// 指针变量指向了其它非法区域,结果意外灾难不幸发生了。catch(…)为这种
// 潜在的隐患提供了一种有效的补救措施。
catch(…)
{
}
}

  还有,特别是VC程序员为了使开发的系统有更好的可靠性,往往在应用程序的入口函数中(如MFC框架的开发环境下CXXXApp::InitInstance())和工作线程的入口函数中加上一个顶层的trycatch块,并且使用catch(…)来捕获一切所有的异常,如下:

BOOL CXXXApp::InitInstance()
{
if (!AfxSocketInit())
{
AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
return FALSE;
}

AfxEnableControlContainer();

// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need.

#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif


// 注意这里有一个顶层的trycatch块,并且使用catch(…)来捕获一切所有的异常
try
{
CXXXDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
}
catch(…)
{
// dump出系统的一些重要信息,并通知管理员查找出现意外异常的原因。
// 同时想办法恢复系统,例如说重新启动应用程序等
}

// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
}

  通过上面的例程和分析可以得出,由于catch(…)能够捕获所有数据类型的异常对象,所以在恰当的地方使用catch(…)确实可以使软件系统有着更好的可靠性。这确实是大家使用catch(…)这个东东最好的理由。但不要误会的是,在C++异常处理模型中,不只有catch(…)方法能够捕获几乎所有类型的异常对象(也许有其它更好的方法,在下一篇文章中主人公阿愚带大家一同去探讨一下),可C++标准中为什么会想到定义这样一个catch(…)呢?有过java或C#编程开发经验的程序员会发现,在它们的异常处理模型中,并没有这样类似的一种语法,可这里不得不再次强调的是,java中的异常处理模型是C++中的异常处理模型的完善改进版,可它反而没有了catch(…),为何呢?还是先去看看下一章吧,“C++的异常处理和面向对象的紧密关系”。也许大家能找到一个似乎合理的原因。
第4集 C++的异常处理和面向对象的紧密关系
--------------------------------------------------------------------------------

CSAI.CN原创 王胜祥 2005年05月16日
  如果有人问起C++和C到底有那些本质上的不同点?主人公阿愚当然也会有自己的一份理解,他会毫不犹豫回答出:“与C相比,C++至少引入了两项重要技术,其一就是对面向对象的全面支持;还有一项就是C++优良的异常处理模型”。是的,这两项技术对构建出一个优良的可靠复杂的软件系统都太重要了。可这两项技术之间又有何关系呢?非常客观公正的说,它们之间的关系实在是太紧密了,两者相互支持和依赖,是构建优良可靠复杂的软件系统最不可缺乏的两个东东。

用对象来描述程序中出现的异常

  虽然前几篇文章的内容中列举的一些小例子程序大多都是throw一些如int、double类型的异常,但程序员朋友都很熟悉,实际开发环境中所抛出的异常都是一个个代表抽象数据类型的对象,如C++标准库中的std::exception(),MFC开发库中Cexception等。用对象来描述的我们程序中的出现异常的类型和异常信息是C++异常处理模型中最闪光之处,而且这一特点一直沿用到java语言的异常处理模型中。

  为什么要用对象来描述程序中出现的异常呢?这样做的优势何在?主人公阿愚不喜欢穷摆出什么大道理,还是老办法,从具体的实例入手。由于异常有许许多多种类型,如有致命的错误、一般的错误、警告或其它事件通知等,而且不同类型的异常有不同的处理方法,有的异常是不可恢复的,而有的异常是可以恢复的(专业术语叫做“重入”吧!哈哈,主人公阿愚有时也会来点文绉绉的东西),所以程序员在开发系统时就必须考虑把各种可能出现的异常进行分类,以便能够分别处理。下面为一个应用系统设计出一个对异常进行分类的简单例子,如下:


user posted image

  从上面的异常分类来看,它有明显的层次性和继承性,这恰恰和面向对象的继承思想如出一辙,因此用对象来描述程序中出现的异常是再恰当不过的了。而且可以利用面向对象的特性很好的对异常进行分类处理,例如有这样一个例子:

void OpenFile(string f)
{
try
{
// 打开文件的操作,可能抛出FileOpenException
}
catch(FileOpenException& fe)
{
// 处理这个异常,如果这个异常可以很好的得以恢复,那么处理完毕后函数
// 正常返回;否则必须重新抛出这个异常,以供上层的调用函数来能再次处
// 理这个异常对象
int result = ReOpenFile(f);
if (result == false) throw;
}
}

void ReadFile(File f)
{
try
{
// 从文件中读数据,可能抛出FileReadException
}
catch(FileReadException& fe)
{
// 处理这个异常,如果这个异常可以很好的得以恢复,那么处理完毕后函数
// 正常返回;否则必须重新抛出这个异常,以供上层的调用函数来能再次处
// 理这个异常对象
int result = ReReadFile(f);
if (result == false) throw;
}
}

void WriteFile(File f)
{
try
{
// 往文件中写数据,可能抛出FileWriteException
}
catch(FileWriteException& fe)
{
// 处理这个异常,如果这个异常可以很好的得以恢复,那么处理完毕后函数
// 正常返回;否则必须重新抛出这个异常,以供上层的调用函数来能再次处
// 理这个异常对象
int result = ReWriteFile(f);
if (result == false) throw;
}
}

void Func()
{
try
{
// 对文件进行操作,可能出现FileWriteException、FileWriteException
// 和FileWriteException异常
OpenFile(…);

ReadFile(…);

WriteFile(…);
}
// 注意:FileException是FileOpenException、FileReadException和FileWriteException
// 的基类,因此这里定义的catch(FileException& fe)能捕获所有与文件操作失败的异
// 常。
catch(FileException& fe)
{
ExceptionInfo* ef = fe.GetExceptionInfo();
cout << “操作文件时出现了不可恢复的错误,原因是:”<< fe << endl;
}
}

  通过上面简单的例子可以看出,利用面向对象的方法,确实能很好地对异常进行分类处理,分层处理,如果异常能得以恢复的尽可能去实现恢复,否则向上层重新抛出异常表明当前的函数不能对这里异常进行有效恢复。同时特别值得一提的是,上层的catch block利用申明一个基类的异常对象作为catch关键字的参数,使得提供了对多种类型的异常对象的集中处理方法,这就是上一篇文章中所提到的除了catch(…)以外,还有其它的来实现对多种类型的异常对象的集中处理方法,而且利用对象基类的方法显然要比catch(…)优雅很多,方便很多,要知道在catch(…)的异常处理模块中是没有多少办法获取一些关于异常出现时异常具体信息的,而对象基类的方法则完全不同,异常处理模块可以访问到真正的异常对象。

  现在回想一下上一篇文章中提出的那个问题?就是既然有其它很好的方法(利用类的继承性)来可以代替catch(…)提供的异常集中处理,那为什么C++标准中还偏要提供catch(…)这样一种奇怪的语法呢?其实这还是由于C++本身一些特点所决定的,因为大家都知道,C++在业界有很多的版本,更重要的是没有一个统一的标准开发类库,或者说没有统一的标准开发环境,虽然存在标准C库和标准C++库,但这远远不够,构成不了一个完整的开发支撑环境,因此在许多重要的开发库中都各自为政,它们在自己的开发库都各自定义了一套对异常进行分类支持的库。因此应用程序的开发环境往往都同时需要依赖于几个基础开发库之上(例如MFC + XML4C + Standard C++),这样对开发人员而言,便没有一个共同的异常对象的基类,所以C++标准中便提供了catch(…)来捕获所有异常,这确实是一种不得已而为之的折衷方法(哈哈!这个理解完全是主人公阿愚自己一相情愿,一个人胡思乱想而出来的,朋友们如有不同的意见可以和阿愚一起讨论!),另外JAVA中则不会出现这种情况,因为JDK是统一的,所有的异常对象都是从java.lang.Throwable接口继承而来的,因此只要在程序的入口函数中catch(java.lang.Throwable all),便可以捕获所有的异常。所以在JAVA的异常处理模型中没有类似C++那样一个catch(…)的东东,完全没必要。

异常处理中采用面向对象技术还有哪些好处呢?

  上面讲到,用对象来描述程序中出现的异常除了能很好地分层处理异常外,还有那些好处呢?当然除了好处大大的外,好处也是多多的,例如:

  (1) 面向对象的实现中,一般都很好的实现了对象的RTTI技术,如果异常用对象来表示,那么就可以很好完成异常对象的数据类型匹配,还有就是函数的多态,例如上面的那个例子中,即便是到了catch(FileException& fe)异常处理模块中,也能知道到底是出现了那种具体的异常,是FileOpenException呢?还是其它的异常?

  (2) 面向对象的实现中,一般都很好的实现了对象的构造、对象的销毁、对象的转存复制等等,所以这也为异常处理模型中,异常对象的转存复制和对象销毁提供了很好的支持,容易控制;
  (3) 其它的吗?暂时没有,可能还有没想到的。

  在异常处理的分层管理下,异常对象的重新抛出往往非常常见,本文中刚才的例子就有这样的情况,但当时仅一笔带过而已,为了表明对它的重视,下一篇文章重点讨论一下异常对象的rethrow处理。
第5集 C++的异常rethrow
--------------------------------------------------------------------------------

CSAI.CN原创 王胜祥 2005年05月16日
  上一篇文章已经提到了C++的异常rethrow现象,这篇文章将进一步深入讨论这个问题。当catch到一个异常后进入异常处理程序块中,此时需要从传入的异常对象中得到一些关于异常的详细信息,并判断这个异常是否能得以恢复,是否能在当前的异常处理程序块中得以处理。如果是,那么及时地处理掉这个异常,使程序恢复到正常的工作轨道上(也即从catch block后面的代码处继续执行);否则就必须重新抛出异常(Excption Rethrow),把这个异常交给上一层函数的异常处理模块去处理(反正我是处理不了了,而且我也通知了我的上层领导,所以责任吗,也就不由我担当了,哈哈 ^-^)。

语法

  很简单,有两种用法,如下:
  1、 throw ;
  2、 throw exception_obj ;
  第一种表示原来的异常对象再次被重新抛出;第二中呢,则表示原来的异常已处理或正在处理中,但此时又引发了另一个异常。示例如下:

void main()
{
try
{
try
{
throw 4;
}
catch(int value)
{
// 第一种用法,原来的异常被再次抛出
// 注意它不需要带参数。
throw;
}

try
{
throw 0.5;
}
catch(double value)
{
// 第二种用法,再次抛出另外的一个异常
// 它的语法和其它正常抛出异常的语法一样。
throw “another exception”;
}
}
catch(...)
{
cout << “unknow exception”<< endl;
}
}

在什么地方异常可以rethrow?

  当然,异常的rethrow只能在catch block中,或者说在catch block中抛出的异常才是异常的rethrow,因此注意下面的示例程序中存在语法错误,如下:

void main()
{
try
{
try
{
throw 4;
}
catch(int value)
{
// 这里的语法是对的。
throw;
}

// 但这里的语法却是不对的。
// 不能在这里进行异常的rethrow
throw;
}
catch(...)
{
cout << “unknow exception”<< endl;
}
}

异常rethrow需要注意的问题!
异常rethrow需要注意什么问题呢?看例子先!

void main()
{
try
{
try
{
throw 4;
}
catch(int value)
{
// 异常的rethrow
throw;
}
catch(...)
{
cout << “能打印我这条消息吗?”<< endl;
}
}
catch(...)
{
cout << “unknow exception”<< endl;
}
}

  上面的程序运行结果是:“unknow exception”
  由此我们可以得出结论,异常的rethrow后,它会在它上一层的trycatch块开始查找匹配的catch block异常处理块,而在同一层中,如果当前的catch block后面还有其它的catch block,它是不会去匹配的。所以程序中一般层次模型的trycatch要比线性结构的trycatch要好一些,如下(示例2要比示例1好):

// 示例1
void main()
{
try
{
}
catch(DataType1&)
{
}
catch(DataType2&)
{
}
catch(DataType3&)
{
}
catch(...)
{
}
}

// 示例2
void main()
{
try
{
try
{
try
{
try
{
}
catch(DataType1&)
{
}
}
catch(DataType2&)
{
}
}
catch(DataType3&)
{
}
}
catch(...)
{
}
}

总结

  相遇篇的文章到此结束。通过这几篇文章的介绍,目前已经对异常处理编程的思想,C++异常处理模型、语法,以及C++异常处理与面向对象的关系等等,都有了一个大概性的了解。主人公阿愚根据自己的理解和经验,现在对相遇篇中的知识再做出如下一些总结:
  (1) 异常处理编程和面向对象,是现在程序设计和编程中最不可缺少的两个好东东;
  (2) C++异常处理模型是层次型的,能很好地支持嵌套;
  (3) C++异常处理编程提供try、catch和throw三个关键字。其中try定义受监控的程序块;catch定义异常处理模块;throw让程序员可以在程序执行出错的地方抛出异常;
  (4) C++异常处理模型的实现充分使用到了面向对象的思想和方法;
  (5) C++异常处理模型中,异常是可以rethrow的。

  从下篇文章开始,对异常处理编程将进入到了一个相知的阶段。这一阶段中,阿愚将全面性地去深入了解异常处理编程中的各个细节和一些特点,并根据自己的理解阐述一些异常处理编程设计思想方面的东西。各位程序员朋友们,准备好了吗?Let's go!
阅读(993) | 评论(0) | 转发(0) |
0

上一篇:异常处理编程(1)

下一篇:C++异常(3)

给主人留下些什么吧!~~