• C++与Object Pascal中的构造函数与异常


    http://www.xici.net/main.asp?url=/u4663391/d2818843.htm

    我们知道,类的构造函数是没有返回值的,如果构造函数构造对象失败,不可能依靠返回错误代码。那么,在程序中如何标识构造函数的失败呢?最“标准”的方法就是:抛出一个异常。

      构造函数失败,意味着对象的构造失败,那么抛出异常之后,这个“半死不活”的对象会被如何处理呢?这就是本文的主题。

      在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()中的throw10;代码),执行结果是:
      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的风格,注重效率,一切交给程序员来掌握,编译器不作多余动作。ObjectPascal继承Pascal的风格,注重程序的美学意义(不可否认,Pascal代码是全世界最优美的代码),编译器帮助程序员完成复杂的工作。两种语言都有存在的理由,都有存在的必要!而掌握它们之间的差别,能让你更好地控制它们,达到自由的理想王国。

  • 相关阅读:
    JavaScript设计模式-21.命令模式
    JavaScript设计模式-20.责任链模式
    JavaScript设计模式-18.享元模式
    JavaScript设计模式-19.代理模式
    JavaScript设计模式-17.装饰者模式(下)
    JavaScript设计模式-16.装饰者模式(上)
    面向对象之集合ArrayList
    面向对象之继承
    字符串的添加与切割~~~
    面向对象中构造函数的小练习
  • 原文地址:https://www.cnblogs.com/chulia20002001/p/1829172.html
Copyright © 2020-2023  润新知