C与C++中的异常处理11

类别:VC语言 点击:0 评论:0 推荐:
1.     异常规格申明

    现在是探索C++标准运行库和Visual C++在头文件<exception>中申明的异常支持的时候了。根据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 <stdio.h>

 

void f() throw()

   {

   throw 1;

   }

 

int main()

   {

   try

      {

      f();

      }

   catch (int)

      {

      printf("caught int\n");

      }

   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):

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

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

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

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

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

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

 

1.4     unexpected()函数指南

    unexpected()函数是标准运行库在头文件<exception>中申明的函数。和其它大部分运行库函数一样,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 <exception>

#include <stdio.h>

#include <stdlib.h>

using namespace std;

 

void my_unexpected_handler()

   {

   printf("in unexpected handler\n");

   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 appear\n");

   return 0;

   }

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

in unexpected handler

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

this line should never appear

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

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

#include <exception>

#include <stdio.h>

#include <stdlib.h>

//using namespace std;

 

void my_unexpected_handler()

   {

   printf("in unexpected handler\n");

   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 appear\n");

   return 0;

   }

    Visual C++不能编译这个程序。查看<exception>表明: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 <exception>

#include <stdio.h>

 

void my_unexpected_handler()

   {

   printf("in unexpected handler\n");

   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 appear\n");

      }

   catch (int)

      {

      printf("program resumed\n");

      }

   return 0;

   }

本文地址:http://com.8s8s.com/it/it2699.htm