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

从类引用构造对象

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

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

    编辑:我应该指出,这是Delphi 2006,因为下面的答案中已经指出,在未来的版本中可能不需要NewInstance调用。然而,就我而言,这是必要的。因此,我想我的问题的答案(它安全吗?CreateForm()是否有潜在的泄漏)需要在这是Delphi 2006的基础上得到回答

    编辑#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。T应用程序。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;
    

    在Forms.pas方法中,这是否意味着当Create()方法中发生异常时内存会泄漏?我的理解是InstanceClass。NewInstance分配了内存,因此在这种情况下,内存没有被释放/释放/释放?

    4 回复  |  直到 17 年前
        1
  •  12
  •   menjaraz    14 年前

    你应该把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    17 年前

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

    首先是持续存在的误解,即构建者需要是虚拟的。确实如此 请考虑以下示例:

    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 Mohan Dere    9 年前

    关于Create()引发异常时内存泄漏的问题:你应该自己尝试一下。我刚刚在Delphi2007上做了这项工作,在您的代码中,FastMM4显示了一个错误对话框,内容是尝试在已释放的对象上调用虚拟方法,即Destroy()。因此,Create中的异常已经导致析构函数被调用,内存被释放,所以你的代码实际上是错误的。坚持使用成语 answer by Gamecat ,一切都应该工作。

    编辑:

    我刚刚在Delphi 4上进行了测试,其表现与之前相同。测试代码:

    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;
    

    对于FastMM4,finally块中的Free会给出相同的错误,因为C已经被释放了。不过,在应用程序关闭时,异常和异常字符串会被报告为内存泄漏。然而,这不是代码的问题,而是运行时的问题。

        4
  •  2
  •   PetriW    17 年前

    编辑:

    不完全记得在旧的delphi版本中是怎么回事,但显然这应该在所有其他回复的基础上都能工作。

    注意,从我记事起,Create就一直在失败时调用Destroy。它不应该在我想之后。

    代码为:

    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;
    

    我删除了原始函数返回的结果,因为它永远不会为false,只能为“未赋值”(异常)或true。毕竟,在将结果赋值为false之前,您可能会遇到异常。 ;)