我有一个构造对象,调用Execute方法并释放对象的方法。对象的类型由传递给方法的TClass后代确定。 请注意,这是我正在谈论的Delphi for Win32,而不是.NET。
编辑:我应该指出这是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.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;
在Forms.pas方法中,这是否意味着在Create()方法中发生异常时内存泄漏?我的理解是InstanceClass.NewInstance分配了内存,因此在这种情况下内存没有被释放/释放/释放?
答案 0 :(得分:12)
你应该把try放在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;
答案 1 :(得分:4)
在评论中提出了一些我想澄清的问题。
首先是构造函数需要虚拟的持续神话。它确实不。考虑这个例子:
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
的完整实例。将分配足够的内存来保存字符串字段,这在基类中不存在。
在您需要虚拟构造函数之前,必须满足两个条件:
TDerived.Create(23)
),那么虚拟方法就无法获得任何东西。在这里要认识到的重要一点是,这两个规则与确定何时使任何其他方法虚拟的因素没有区别。在这方面,构造者并不特别。
构造函数知道要构造的类不是基于定义构造函数的类,而是基于类的构造函数被调用,并且该类总是作为每个构造函数调用的隐藏的第一个参数传递。
其次是应该调用NewInstance
代替构造函数还是除了构造函数之外的问题。我认为其他评论已经确定它与旧版Delphi版本的兼容性无关。 所有版本都支持在类引用上调用构造函数,而不需要NewInstace
。相反,混淆来自于TApplication.CreateForm
并将其视为应如何完成事情的一个例子。这是一个错误。
CreateForm
在调用构造函数之前调用NewInstance
,因为CreateForm
存在的主要原因是确保IDE在表单自己的事件处理程序中声明的全局表单变量有效,包括OnCreate
,它作为构造函数的一部分运行。如果CreateForm
方法已经完成了通常的构造模式,那么全局表单变量将不具有有效值。以下是您可能希望看到的内容:
TComponent(Reference) := InstanceClass.Create(Application);
简单而明显,但这不起作用。 {<1}}在构造函数返回之后才会被分配一个值,这在表单触发某些事件之后很久。如果您遵循良好的编程习惯并且从不在表单类本身中引用该变量,那么您将永远不会注意到。但是,如果您按照为没有经验的受众编写的文档说明,那么将从表单自己的方法中引用全局表单变量,因此Reference
方法执行它的操作可以确保及时分配。
为此,它使用两步构造技术。首先,分配内存并将引用分配给全局变量:
CreateForm
接下来,调用实例上的构造函数,将Instance := TComponent(InstanceClass.NewInstance);
TComponent(Reference) := Instance;
对象作为所有者传递:
TApplication
It's my opinion Instance.Create(Self);
应该在任何程序中只调用一次CreateForm
。我更喜欢零次,但它有定义Application.MainForm
的副作用,这对于Delphi程序的其他方面很重要。
第三种观点是,对象自身调用构造函数是不常见的。
事实上,这种情况一直发生 。每次调用继承的构造函数时,都会在已存在的对象上调用构造函数。继承的构造函数未分配新对象。同样,VCL有一些非继承的构造函数调用的例子。 TCustomForm.Create
将其大部分构造任务委托给CreateNew
构造函数。
答案 2 :(得分:2)
修改:
没有完全记住它在旧的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,只有“unassigned”(异常)或true。在将结果赋值为false之前,您可以获得异常。 ;)
答案 3 :(得分:2)
当Create()引发异常时,重新提出有关内存泄露的问题:您应该自己尝试一下。我刚刚在Delphi 2007上做过,并且使用你的代码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已经被释放了。但是,在应用程序关闭时,异常和异常字符串将报告为内存泄漏。然而,这不是代码的问题,而是运行时。