首页  编辑  

构造函数和异常

Tags: /超级猛料/Language.Object Pascal/面向对象和类、VCL核心/   Date Created:

构造函数与异常

节选自《Delphi高手突破》,Nicrosoft著

这个话题在C++社区中经常会被提起,而在Delphi社区中似乎从来没有人注意过。也许由

于语言的特性,使得Delphi程序员不必关心这个问题。但我想Delphi程序员也应该对该问

题有所了解,知道语言为我们提供了什么而使得我们如此轻松,不必理会它。正所谓"身

在福中须知福"。

我们知道,类的构造函数是没有返回值的,如果构造函数构造对象失败,不可能依靠返回错误代

码。那么,在程序中如何标识构造函数的失败呢?最"标准"的方法就是:抛出一个异常。

  构造函数失败,意味着对象的构造失败,那么抛出异常之后,这个"半死不活"的对象会被如何

处理呢?这就是本文的主题。

  在C++中,构造函数抛出异常后,析构函数不会被调用。这是合理的,因为此时对象并没有被完整

构造。也就是说,如果构造函数已经做了一些诸如分配内存、打开文件等操作的话,那么类需要有自

己的成员来记住做过哪些动作。在C++中,经典的解决方案是使用STL的标准类auto_ptr,这在每一本

经典C++著作中都有介绍,我在这里就不多说了。在这里,我想再介绍一种"非常规"的方式,其思想

就是避免在构造函数中抛出异常。我们可以在类中增加一个 Init(); 以及 UnInit();成员函数用于进

行容易产生错误的资源分配工作,而真正的构造函数中先将所有成员置为NULL,然后调用 Init(); 并

判断其返回值(或者捕捉 Init()抛出的异常),如果Init();失败了,则在构造函数中调用 UnInit

(); 并设置一个标志位表明构造失败。UnInit()中按照成员是否为NULL进行资源的释放工作。示例代

码如下:

class A

{

private:

       char* str;

       int failed;

public:

       A();

       ~A();

       int Init();

       int UnInit();

       int Failed();

};

A::A()

{

       str = NULL;

       try

       {

               Init();

               failed = 0;

       }

       catch(...)

       {

               failed = 1;

               UnInit();

       }

}

A::~A()

{

       UnInit();

}

int A::Init()

{

       str = new char[10];

       strcpy(str, "ABCDEFGHI");

       throw 10;

       return 1;

}

int A::UnInit()

{

       if (!str)

       {

               delete []str;

               str = NULL;

       }

       printf("Free Resource\n");

       return 1;

}

int A::Failed()

{

       return failed;

}

int main(int argc, char* argv[])

{

       A* a = new A;

       if ( a->Failed() )

               printf("failed\n");

       else

               printf("succeeded\n");

       delete a;

       getchar();

       return 0;

}

  你会发现,在int A::Init()中包含了throw 10;的代码(产生一个异常,模拟错误的发生),执

行结果是:

  Free Resource

  failed

  Free Resource

  虽然 UnInit();被调用了两次,但是由于UnInit();中做了判断(if (!str)),因此不会发生错

误。而如果没有发生异常(去掉 int A::Init()中的throw 10;代码),执行结果是:

  Succeeded

  Free Resource

  和正常的流程没有任何区别。

  在Object Pascal(Delphi/VCL)中,这个问题就变得非常的简单了,因为 OP 对构造函数的异常

的处理与C++不同,在Create时抛出异常后,编译器会自动调用析构函数Destroy,并且会判断哪些资

源被分配了,实行自动回收。因此,其代码也变得非常简洁,如下:

type

 A = class

 private

  str : PChar;

 public

  constructor Create();

  destructor Destroy(); override;

 end;

constructor A.Create();

begin

  str := StrAlloc(10);

  StrCopy(str, 'ABCDEFGHI');

  raise Exception.Create('error');

end;

destructor A.Destroy();

begin

  StrDispose(str);

  WriteLn('Free Resource');

end;

var oa : A;

  i : integer;

begin

  try

      oa := A.Create();

      WriteLn('Succeeded');

      oa.Free();

  except

      oa := nil;

      WriteLn('Failed');

  end;

  Read(i);

end.

  在这段代码中,如果构造函数抛出异常(即Create中含有raise Exception.Create

('error');),执行的结果是:

  Free Resource

  Failed

  此时的"Free Resource"输出是由编译器自动调用析构函数所产生的。而如果构造函数正常返回

(即不抛出异常),则执行结果是:

  Succeeded

  Free Resource

  此时的"Free Resource"输出是由 oa.Free()的调用产生的。

  综上,C++与Object Pascal对于构造函数抛出异常后的不同处理方式,其实正是两种语言的设计

思想的体现。C++秉承C的风格,注重效率,一切交给程序员来掌握,编译器不作多余动作。Object

Pascal继承Pascal的风格,注重程序的美学意义(不可否认,Pascal代码是全世界最优美的代码),

编译器帮助程序员完成复杂的工作。两种语言都有存在的理由,都有存在的必要!而掌握它们之间的

差别,能让你更好地控制它们,达到自由的理想王国。