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

类别:VC语言 点击:0 评论:0 推荐:
1.     从私有子对象中产生的异常

    几部分来,我一直展示了一些技巧来捕获从对象的构造函数中抛出的异常。这些技巧是在异常从构造函数中漏出来后处理它们。有时,调用者需要知道这些异常,但通常(如我所采用的例程中)异常是从调用者并不关心的私有子对象中爆发的。使得用户要关心“不可见”的对象表明了设计的脆弱。

    在历史上,(可能抛异常)的构造函数的实现者没有简单而健壮的解决方法。看这个简单的例子:

#include <stdlib.h>

 

class buffer

   {

public:

   explicit buffer(size_t);

   ~buffer();

private:

   char *p;

   };

 

buffer::buffer(size_t const count)

      : p(new char[count])

   {

   }

 

buffer::~buffer()

   {

   delete[] p;

   }

 

static void do_something_with(buffer &)

   {

   }

 

int main()

   {

   buffer b(100);

   do_something_with(b);

   return 0;

   }

 

    buffer的构造函数接受字符数目并从自由空间分配内存,然后初始化buffer::p指向它。如果分配失败,构造函数中的new语句产生一个异常,而buffer的用户(这里是main函数)必须捕获它。

 

1.1     try块

    不幸的是,捕获这个异常不是件容易事。因为抛出来自buffer::buffer,所有buffer的构造函数的调用应该被包在try块中。没脑子的解决方法:

try

   {

   buffer b(count);

   }

catch (...)

   {

   abort();

   }

do_something_with(b); // ERROR. At this point,

                      //   'b' no longer exists

是不行的。do_something_with()的调用必须在try块中:

try

   {

   buffer b(100);

   do_something_with(b);

   }

catch (...)

   {

   abort();

   }

//do_something_with(b);

    (免得被说闲话:我知道调用abort()来处理这个异常有些过份。我只是用它做个示例,因为现在关心的是捕获异常而不是处理它。)

    虽然有些笨拙,但这个方法是有效的。接着考虑这样的变化:

static buffer b(100);

 

int main()

   {

//   buffer b(100);

   do_something_with(b);

   return 0;

   }

    现在,b被定义为全局对象。试图将它包入try块

try // um, no, I don't think so

   {

   static buffer b;

   }

catch (...)

   {

   abort();

   }

 

int main()

   {

   do_something_with(b);

   return 0;

   }

将不能被编译。

 

1.2     暴露实现

    每个例子都显示了buffer设计上的基本缺陷:buffer的接口以外的实现细节被暴露了。在这里,暴露的细节是buffer的构造函数中的new语句可能失败。这个语句用于初始化私有子对象buffer::p――一个main函数和其它用户不能操作甚至根本不知道的子对象。当然,这些用户更不应该被要求必须关注这样的子对象抛出的异常。

    为了改善buffer的设计,我们必须在构造函数中捕获异常:

#include <stdlib.h>

 

class buffer

   {

public:

   explicit buffer(size_t);

   ~buffer();

private:

   char *p;

   };

 

buffer::buffer(size_t const count)

      : p(NULL)

   {

   try

      {

      p = new char[count];

      }

   catch (...)

      {

      abort();

      }

   }

 

buffer::~buffer()

   {

   delete[] p;

   }

 

static void do_something_with(buffer &)

   {

   }

 

int main()

   {

   buffer b(100);

   do_something_with(b);

   return 0;

   }

 

    异常被包含在构造函数中。用户,比如main()函数,从不知道异常存在过,世界又一次清静了。

 

1.3     常量成员

    也这么做?注意,buffer::p一旦被设置过就不能再被改动。为避免指针被无意改动,谨慎的设计是将它申明为const:

class buffer

   {

public:

   explicit buffer(size_t);

   ~buffer();

private:

   char * const p;

   };

很好,但到了这步时:

buffer::buffer(size_t const count)

   {

   try

      {

      p = new char[count]; // ERROR

      }

   catch (...)

      {

      abort();

      }

   }

 

    一旦被初始化,常量成员不能再被改变,即使是在包含它们的对象的构造函数体中。常量成员只能被构造函数的成员初始化列表设置一次。

buffer::buffer(size_t const count)

      : p(new char[count]) // OK

这让我们回到了段落一中,又重新产生了我们最初想解决的问题。

    OK,这么样如何:不用new语句初始化p,换成用内部使用new的辅助函数来初始化它:

char *new_chars(size_t const count)

   {

   try

      {

      return new char[count];

      }

   catch (...)

      {

      abort();

      }

   }

 

buffer::buffer(int const count)

      : p(new_chars(count))

   {

 

//   try

//      {

//      p = new char[count]; // ERROR

//      }

//   catch (...)

//      {

//      abort();

//      }

   }

    这个能工作,但代价是一个额外函数却仅仅用来保护一个几乎从不发生的事件。

 

1.4     函数try块

(WQ注:后面会讲到,function try块不能阻止构造函数的抛异常动作,它其实只起异常过滤的功能!!!见P14.3

    我在上面这些建议中没有发现哪个能确实令人满意。我所期望的是一个语言级的解决方案来处理部分构造子对象问题,而又不引起上面说到的问题。幸运的是,语言中恰好包含了这样一个解决方法。

    在深思熟虑后,C++标准委员会增加了一个叫做“function try blocks”的东西到语言规范中。作为try块的堂兄弟,函数try块捕获整个函数定义中的异常,包括成员初始化列表。不用奇怪,因为语言最初没有被设计了支持函数try块,所以语法有些怪:

buffer::buffer(size_t const count)

try

      : p(new char[count])

   {

   }

catch

   {

   abort();

   }

看起来想是通常的try块后面的{}实际上是划分构造函数的函数体的。在效果上,{}有双重作用,不然,我们将面对更别扭的东西:

buffer::buffer(int const count)

try

      : p(new char[count])

   {

   {

   }

   }

catch

   {

   abort();

   }

    (注意:虽然嵌套的{}是多余的,这个版本能够编译。实际上,你可以嵌套任意重{},直到遇到编译器的极限。)

    如果在初始化列表中有多个初始化,我们必须将它们放入同一个函数try块中:

buffer::buffer()

try

   : p(...), q(...), r(...)

   {

   // constructor body

   }

catch (std::bad_alloc)

   {

   // ...

   }

    和普通的try块一样,可以有任意个异常处理函数:

buffer::buffer()

try

   : p(...), q(...), r(...)

   {

   // constructor body

   }

catch (std::bad_alloc)

   {

   // ...

   }

catch (int)

   {

   // ...

   }

catch (...)

   {

   // ...

   }

 

    古怪的语法之外,函数try块解决了我们最初的问题:所有从buffer子对象的构造函数抛出的异常留在了buffer的构造函数中。

    因为我们现在期望buffer的构造函数不抛出任何异常,我们应该给它一个异常规格申明:

explicit buffer(size_t) throw();

    接着一想,我们应该是个更好点的程序员,于是给我们所有函数加了异常规格申明:

class buffer

   {

public:

   explicit buffer(size_t) throw();

   ~buffer() throw();

   // ...

   };

 

// ...

 

static void do_something_with(buffer &) throw()

 

// ...

 

Rounding Third and Heading for Home

    对我们的例子,最终版本是:

#include <stdlib.h>

 

class buffer

   {

public:

   explicit buffer(size_t) throw();

   ~buffer() throw();

private:

   char *const p;

   };

 

buffer::buffer(size_t const count)

try

      : p(new char[count])

   {

   }

catch (...)

   {

   abort();

   }

 

buffer::~buffer()

   {

   delete[] p;

   }

 

static void do_something_with(buffer &) throw()

   {

   }

 

int main()

   {

   buffer b(100);

   do_something_with(b);

   return 0;

   }

    用Visual C++编译,自鸣得意地坐下来,看着IDE的提示输出。

syntax error : missing ';' before 'try'

syntax error : missing ';' before 'try'

'count' : undeclared identifier

'<Unknown>' : function-style initializer appears

   to be a function definition

syntax error : missing ';' before 'catch'

syntax error : missing ';' before '{'

missing function header (old-style formal list?)

 

    噢!

    Visual C++还不支持函数try块。在我测试过的编译器中,只有Edison Design Group C++ Front End version 2.42认为这些代码合法。

    (顺便提一下,我特别关心为什么编译将第一个错误重复了一下。可能它的计算你第一次会不相信。)

    如果你坚持使用Visual C++,你可以使用在介绍函数try块前所说的解决方法。我喜欢使用额外的new封装函数。如果你认同,考虑将它做成模板:

template <typename T>

T *new_array(size_t const count)

   {

   try

      {

      return new T[count];

      }

   catch (...)

      {

      abort();

      }

   }

 

// ...

 

buffer::buffer(size_t const count)

      : p(new_array<char>(count))

   {

   }

    这个模板比原来的new_chars函数通用得多,对char以外的类型也有能工作。同时,它有一个隐蔽的异常相关问题,而我将在下次谈到。

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