ITPub博客

首页 > 应用开发 > IT综合 > C与C++中的异常处理11 (转)

C与C++中的异常处理11 (转)

原创 IT综合 作者:worldblog 时间:2007-12-12 15:14:41 0 删除 编辑
C与C++中的异常处理11 (转)[@more@]

1.  异常规格申明XML:namespace prefix = o ns = "urn:schemas-microsoft-com:Office:office" />

  现在是探索C++标准运行库和Visual C++在头文件中申明的异常支持的时候了。根据C++标准(subclause 18.6,“Exception handling” )上的描述,这个头文件申明了:

l  从运行库中抛出的异常对象的基类。

l  任何抛出的违背异常规格申明的对象的可能替代物。

l  在违背异常规格申明的异常被抛出是被调用函数,以及在其行为上增加东西的钩子(“hook”)。

l  在异常处理过程被终止时被调用的函数,以及在其行为上增加东西的钩子。

  我从分析异常规格申明及程序违背它时遭到什么可怕后果开始。分析将针对上面提到的主题,以及通常C++异常处理时的一些杂碎。

 

1.1  异常规格申明回顾

  异常规格申明是C++函数申明的一部分,它们指定了函数可以抛出什么异常。例如,函数

void f1() throw(int)

可以抛出一个整型异常,而

void f2() throw(char *, E)

可以抛出一个char *或一个E(这里E是用户自定义类型)类型的异常。一个空的规格申明

void f3() throw()

表明函数不抛出异常,而没有规格申明

void f4()

表明函数可以抛出任何东西。注意语法

void f4() throw(...)

比前面的“抛任何东西”的函数更好,因为它类似“捕获任何东西”

catch(...)

然而,认可“抛任何东西” 的函数就允许了那些在异常规格申明存在前写下的函数。

 

1.2  违背异常规格申明

  迄今为止,我写的都是:函数可能抛出在它的异常规格申明中描述的异常。“可能”有些单薄,“必须”则有力些。“可能”表示了函数可以忽略它们的异常规格。你也许认为编译器将禁止这种行为:

void f() throw() // Promises not to throw...

  {

  throw 1;  // ...but does anyway - error?

  }

但你错了。用Visual C++试一下,你将发现编译器保持沉默,它没有发现编译期错误。实际上,在我所用过的编译器中,没有一个报了编译期错误。

  话虽这么说,但异常规格申明有它的规则的,函数违背它将遭受严重后果的。不幸的是,这些后果表现在运行期错误而不是编译期。想看的话,把上面的小段代码放到一个完整程序中:

void f() throw()

  {

  throw 1;

  }

 

int main()

  {

  f();

  return 0;

  }

 

  当程序运行时将发生什么?f()抛出一个int型异常,违背了它的契约。你可能认为这个异常将从main()中漏入运行期库。基于这个假设,你倾向于使用一个简单的try块:

#include

 

void f() throw()

  {

  throw 1;

  }

 

int main()

  {

  try

  {

  f();

  }

  catch (int)

  {

  printf("caught intn");

  }

  return 0;

  }

 

来捕获这个异常,以防止它漏出去。

  实际上,如果你用Visual C++ 6编译并运行,你将得到:

caught int

  你再次奇怪throw()异常规格实际做了什么有用的事,除了增加了源代码的大小和看起来比较快感。你的奇怪感觉将变得迟钝,只要一回想到前面说了多少Visual C++违背C++标准的地方,只不过再多一个新问题:Visaul C++正确地处理了违背异常规格申明的情况了吗?

 

1.3  调查说明……

  没有!

  这个程序的行为符合标准吗?catch语句不该进入的。来自于标准(subclauses 15.5.2 and 18.6.2.2):

一个异常规格申明保证只有被列出的异常被抛出。

如果带异常规格申明的函数抛出了一个没有列出的异常,函数

void unexpected()在退完栈后立即被调用。

函数unexpected()将不会返回……

  当一个函数试图抛出没有列出的异常时,通过unexpected()函数调用了一个异常处理函数。这个异常处理函数的默认实现是调用teRminate() 来结束程序。

  在我给你一个简短的例程后,我将展示Visual C++的行为怎么样地和标准不同。

 

1.4  unexpected()函数指南

  unexpected()函数是标准运行库在头文件中申明的函数。和其它大部分运行库函数一样,unexpected()函数存在于命名空间std中。它不接受参数,也不返回任何东西,实际上unexpected()函数从不返回,就象abort()和exit()一样。如果一个函数违背了它自己的异常规格申明,unexpected()函数在退完栈后被立即调用。

  基于我对标准的理解,运行库的unexpected()函数的实现理论上是这样的:

void _default_unexpected_handler_()

  {

  std::terminate();

  }

 

std::unexpected_handler _unexpected_handler =

  _default_unexpected_handler;

 

void unexpected()

  {

  _unexpected_handler();

  }

  (_default_unexpected_handler和_unexpected_handler是我虚构的名字。你的运行库的实现可能使用其它名称,完全取决于其实现。)

  std::unexpected()调用一个函数来真正处理unexpected的异常。它通过一个隐藏的指针(_unexpected_handler,类型是std::unexpected_handler)来引用这个处理函数的。运行库提供了一个默认处理函数(default_unexpected_handler()),它调用std::terminate()来结束程序。

  因为是通过指针_unexpected_handler间接调用的,你可以将内置的调用_default_unexpected_handler改为调用你自己的处理函数,只要这个处理函数的类型兼容于std::unexpected_handler:

typedef void (*unexpected_handler)();

  同样,处理函数必须不返回到它的调用者(std::unexpected())中。没人阻止你写一个会返回的处理函数,但这样的处理函数不是标准兼容的,其结果是程序的行为有些病态。

  你可以通过标准运行库的函数std::set_unexpected()来挂接自己的处理函数。注意,运行库只维护一个处理函数来处理所有的unexpected异常;一旦你调用了set_unexpected()函数,运行库将不再记得前一次的处理函数。(和atexit()比较一下,atexit()至少可以挂32重exit处理函数。)要克服这个限制,你要么在不同的时间设置不同的处理函数,要么使你的处理函数在不同的上下文时有不同的行为。

 

1.5  Visual C++ vs unexpected

  试一下这个简单的例子:

#include

#include

#include

using namespace std;

 

void my_unexpected_handler()

  {

  printf("in unexpected handlern");

  abort();

  }

 

void throw_unexpected_exception() throw(int)

  {

  throw 1L; // violates specification

  }

 

int main()

  {

  set_unexpected(my_unexpected_handler);

  throw_unexpected_exception();

  printf("this line should never appearn");

  return 0;

  }

  用一个标准兼容的编译器编译并运行,程序结果是:

in unexpected handler

可能接下来是个异常异常终止的特殊(因为有abort()的调用)。但用Visual C++编译并运行,程序会抛出“Unhandled exception”对话框。关闭对话框后,程序输出:

this line should never appear

  必须承认,Visual C++没有正确实现unexpected()。这个函数被申明在中,运行期库中有其实现,只不过这个实现不做任何事。

  实际上,Visual C++甚至没有正确地申明,用这个理论上等价的程序可以证明:

#include

#include

#include

//using namespace std;

 

void my_unexpected_handler()

  {

  printf("in unexpected handlern");

  abort();

  }

 

void throw_unexpected_exception() throw(int)

  {

  throw 1L; // violates specification

  }

int main()

  {

  std::set_unexpected(my_unexpected_handler);

  throw_unexpected_exception();

  printf("this line should never appearn");

  return 0;

  }

  Visual C++不能编译这个程序。查看表明:set_unexpected_handler()被申明为全局函数而不是在命名空间std中。实际上,所有的unexpected族函数都被申明为全局函数。

  底线:Visual c++能编译使用unexpected()等函数的程序,但运行时的行为是不正确的。

  我希望Microsoft能在下一版中改正这些问题。在未改正前,当讨论涉及到unexpected()时,我建议你使用标准兼容的C++编译器。

 

1.6  维持程序存活

  在我所展示的简单例子中,程序在my_unexpected_handler()里停止了。有时,让程序停止是合理和正确的;但更多情况下,程序停止是太刺激了,尤其是当unexpected异常表明的是程序只轻微错误。

  假定你想处理unexpected异常,并恢复程序,就象对大多数其它“正常”异常一样。因为unexpected()从不返回,程序恢复似乎不可能,除非你看了标准的subclause 15.5.2:

  unexpected()不该返回,但它可以throw(或re-throw)一个异常。如果它抛出一个新异常,而这异常是异常规格申明允许的,搜索另外一个异常处理函数的行为在调用unexpected()的地方继续进行。

  太好了!如果my_unexpected_handler()抛出一个允许的异常,程序就能从最初的违背异常规格申明的地方恢复了。在我们的例子里,最初的异常规格申明允许int型的异常。根据上面的说法,如果my_unexpected_handler抛出一个int异常,程序将能继续了。

  基于这种猜测,试一下:

#include

#include

 

void my_unexpected_handler()

  {

  printf("in unexpected handlern");

  throw 2; // allowed by original specification

  //abort();

  }

  用标准兼容的编译器编译运行,程序输出:

in unexpected handler

program resumed

  和期望相符。

  抛出的int异常和其它异常一样顺调用链传递,并被第一个相匹配的异常处理函数捕获。在我们的例子里,程序的控制权从my_unexpected_handler()向std::unexpected()再向main()回退,并在main()中捕获异常。用这种方法,my_unexpected_handler()变成了一个异常转换器,将一个最初的“坏”的long型异常转换为一个“好”的int型异常。

  结论:通过转换一个unexpected异常为expected异常,你能恢复程序的运行。

 

1.7  预告

  下次,我将结束std::unexpected()的讨论:揭示在my_unexpected_handler()中抛异常的限制,探索运行库对这些限制的补救,并给出处理unexpected异常的通行指导原则。我也将开始讨论运行库函数std::terminate()的相关内容。

 

 

void throw_unexpected_exception() throw(int)

  {

  throw 1L; // violates specification

  }

 

int main()

  {

  std::set_unexpected(my_unexpected_handler);

  try

  {

  throw_unexpected_exception();

  printf("this line should never appearn");

  }

  catch (int)

  {

  printf("program resumedn");

  }

  return 0;

  }

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10752043/viewspace-992217/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
  • 博文量
    6241
  • 访问量
    2410642