代码之家  ›  专栏  ›  技术社区  ›  Graza

从类引用构造对象

  •  3
  • Graza  · 技术社区  · 16 年前

    我有一个方法,它构造一个对象,调用一个Execute方法,并释放该对象。对象的类型由传递到方法中的TClass子体确定。 注意,我说的是针对Win32的Delphi,而不是.NET。

    编辑#2:似乎D2007&D2009实际上为D2006工作。我一定是从Delphi的早期版本中养成了“NewInstance”的习惯。。。

    function TPageClassFactory.TryExecute(ScrnClass: TCustomPageClass): boolean;
    //TCustomPageClass = class of TCustomPage
    var
      ScrnObj: TCustomPage; //TCustomPage defines an abstract Execute() method
    begin
      Result := FALSE; //default
      ScrnObj := TCustomPage(ScrnClass.NewInstance); //instantiate
      try
        ScrnObj.Create(Self);  //NB: Create() and Execute() are *virtual* methods
        ScrnObj.Execute;       
      finally
        FreeAndNil(ScrnObj);
      end;
      Result := TRUE;
    end;
    

    我想知道的是这是否安全——如果Create()引发异常,这里会发生什么?

    看一个类似的例子,从Forms.pas.TApplication.CreateForm()开始,我们采用了一种不同的方法来处理异常(我删去了下面不相关的部分):

    procedure TApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
    var
      Instance: TComponent;
    begin
      Instance := TComponent(InstanceClass.NewInstance);
      TComponent(Reference) := Instance;
      try
        Instance.Create(Self);
      except
        TComponent(Reference) := nil;
        raise;
      end;
    end;
    

    4 回复  |  直到 16 年前
        1
  •  12
  •   menjaraz    13 年前

    您应该将create放在try finally块之外。

    type 
      TMyClass = class ()
      public
        constructor Create(...); virtual;
        function Execute: Boolean; virtual;
      end;
      TMyClassClass = class of TMyClass;
    
    
    procedure CreateExecute(const AClass: TMyClassClass): Boolean;
    var
      theclass : TMyClass;
    begin
      theclass := AClass.Create;
      try
        Result := theclass.Execute;
      finally
        theclass.Free;
      end;
    end;
    
        2
  •  4
  •   Rob Kennedy    16 年前

    评论中提出了一些问题,我想澄清一下。

    type
      TBase = class
        constructor Create(x: Integer);
      end;
      TDerived = class(TBase)
        field: string;
      end;
      TMetaclass = class of TBase;
    
    var
      instance: TBase;
      desiredClass: TMetaclass;
    begin
      desiredClass := TDerived;
      instance := desiredClass.Create(23);
      Assert(instance.ClassName = 'TDerived');
      Assert(instance is TDerived);
      Assert(instance.field = '');
    end;
    

    创建的对象将是类的完整实例 TDerived . 将分配足够的内存来保存基类中不存在的字符串字段。

    在需要虚拟构造函数之前,必须满足两个条件:

    1. TDerived.Create(23) ),那么虚拟方法就什么也得不到了。
    2. 基类的子类需要重写构造函数以提供特定于类的初始化。如果所有的子体使用相同的构造,并且只在其他方法中有所不同,那么就没有必要使构造函数成为虚拟的。

    这里需要认识到的重要一点是,这两条规则与决定何时使任何其他方法成为虚拟方法的因素没有区别。构造函数在这方面并不特别。

    构造函数知道构造哪个类不是基于定义构造函数的类,而是基于调用构造函数的类,并且该类始终作为每个构造函数调用的隐藏第一个参数传递。


    NewInstance 应在构造函数的位置或之外调用。我认为其他评论已经证实,它与旧的Delphi版本的兼容性无关。 NewInstace TApplication.CreateForm 并将其作为应如何行事的范例。那是个错误。

    CreateForm 电话 创造形式 存在的主要原因是确保IDE声明的全局表单变量在表单自身的事件处理程序期间有效,包括 OnCreate 创造形式 方法已经完成了通常的构造模式,那么全局表单变量还没有有效的值。以下是您可能希望看到的内容:

    TComponent(Reference) := InstanceClass.Create(Application);
    

    Reference 之后 从表单自身的方法中引用全局表单变量,因此 方法尽其所能确保及时分配。

    Instance := TComponent(InstanceClass.NewInstance);
    TComponent(Reference) := Instance;
    

    接下来,调用实例上的构造函数,传递 TApplication

    Instance.Create(Self);
    

    It's my opinion 那个 创造形式 在任何程序中都应该只调用一次。我更喜欢零次,但它有定义的副作用 Application.MainForm ,这对于Delphi程序的其他方面很重要。


    事实上,这种情况经常发生 总是 . 每次调用继承的构造函数时,都是在调用已经存在的对象上的构造函数。继承的构造函数未分配新对象。同样,VCL也有一些构造函数的非继承调用示例。 TCustomForm.Create 将其大部分建设任务委托给 CreateNew

        3
  •  2
  •   Community CDub    7 年前

    当Create()引发异常时,关于内存泄漏的问题:您应该自己尝试一下。我刚刚在Delphi 2007上做过,在代码中,FastMM4显示了一个错误对话框,该对话框关于在已释放的对象上调用虚拟方法的尝试,即Destroy()。因此Create中的异常已经导致调用析构函数并释放内存,因此您的代码实际上是错误的。坚持用英语中的成语 answer by Gamecat ,一切都会好起来的。

    编辑:

    我刚刚在Delphi4上试过,行为也一样。测试代码:

    type
      TCrashComp = class(TComponent)
      public
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;
      end;
    
    constructor TCrashComp.Create(AOwner: TComponent);
    begin
      inherited Create(AOwner);
      raise Exception.Create('foo');
    end;
    
    destructor TCrashComp.Destroy;
    begin
      Beep;
      inherited Destroy;
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      C: TComponent;
    begin
      C := TComponent(TCrashComp.NewInstance);
      try
        C.Create(nil);
        C.Tag := 42;
      finally
        C.Free;
      end;
    end;
    

        4
  •  2
  •   PetriW    16 年前

    我不完全记得它是如何在旧的德尔福版本,但显然这应该在所有基于其他答复的工作。

    注意,从我记事起,Create就一直在调用Destroy on fail。我想不应该是在我死后。

    procedure TPageClassFactory.TryExecute(ScrnClass: TCustomPageClass);
    var
      ScrnObj: TCustomPage;
    begin
      ScrnObj := ScrnClass.Create(Self);  // Exception here calls the destructor
      try
        ScrnObj.Execute; // Exception here means you need to free manually      
      finally
        FreeAndNil(ScrnObj); // Be free!
      end;
    end;