Delphi中的"free"是什么作用?

5

我在这里找到了以下代码片段

with TClipper.Create do
  try
    AddPolygon(subject, ptSubject);
    AddPolygon(clip, ptClip);
    Execute(ctIntersection, solution);
  finally
    free;
  end

我很好奇,在 finallyend 之间的 free 语句/函数在这里的作用是什么?Google 没有帮助。


6
警惕语句可能会导致一些非常误导性的错误。比如代码位于TForm1这样的另一个对象内部,那么是调用TClipper.Free还是TForm1.Free方法?使用with语句会优先考虑作用域,但开发人员经常会忽略这一点,从而导致意外的错误。 - Robert Love
4
@Robert Love:但是上面展示的结构非常标准。 - Andreas Rejbrand
9
“with” 语句适用于那些知道自己在做什么、并且知道如何安全使用危险工具的开发人员。你可以用链锯砍断一条腿,甚至自己的腿。如果你不知道如何使用链锯,请不要使用它。但如果你需要砍伐一棵树并且知道如何使用链锯,那么你将比使用安全剃须刀更快地完成工作。那些说“永远不要使用 with”的人是不值得信任的,因为他们甚至不信任自己。 - Deltics
4
据我所知,“with”语句是Object Pascal/Delphi语言的标准元素,规范明确规定了操作的优先级。因此,我认为我们没有理由不使用它。它并不是一种可能在升级编译器时停止工作的技巧。 - migajek
5
@migajet:当升级RTL/VCL时,它可能会停止。例如,如果您在打开TButton作用域的with语句中访问了一个本地变量“Images”。当您升级到Delphi 2009时,“Images”将不再引用本地变量,因为TButton现在具有被引用的“Images”属性。 - Andreas Hausladen
显示剩余11条评论
6个回答

24

代码

with TClipper.Create do
  try
    AddPolygon(subject, ptSubject);
    AddPolygon(clip, ptClip);
    Execute(ctIntersection, solution);
  finally
    free;
  end

是...的简写

with TClipper.Create do
begin
  try
    AddPolygon(subject, ptSubject);
    AddPolygon(clip, ptClip);
    Execute(ctIntersection, solution);
  finally
    free;
  end;
end;

TClipper.Create 创建了一个类型为 TClipper 的对象,并返回该对象及其方法和属性。使用 with 语句,可以在不使用 NameOfObject.MethodOrProperty 语法的情况下访问此 TClipper 对象的方法和属性。这与大多数编程语言中的使用方式相同。

(一个更简单的例子:

MyPoint.X := 0;
MyPoint.Y := 0;
MyPoint.Z := 0;
MyPoint.IsSet := true;

可以简化为
with MyPoint do
begin
  X := 0;
  Y := 0;
  Z := 0;
  IsSet := true;
end;

)

但在您的情况下,您永远不需要声明TClipper对象作为变量,因为您创建它并可以通过with结构访问其方法和属性。

因此,您的代码几乎相当于

var
  Clipper: TClipper;

Clipper := TClipper.Create;
Clipper.AddPolygon(subject, ptSubject);
Clipper.AddPolygon(clip, ptClip);
Clipper.Execute(ctIntersection, solution);
Clipper.Free;

第一行,Clipper := TClipper.Create,创建了一个TClipper对象。以下三行操作这个对象,然后Clipper.Free销毁了这个对象,释放了被TClipper对象使用的RAM和可能的CPU时间和OS资源。

但是上述代码不好,因为如果在AddPolygonExecute中发生错误(创建异常),那么Clipper.Free将永远不会被调用,导致内存泄漏。为了防止这种情况,Delphi使用try...finally...end结构:

Clipper := TClipper.Create;
try
  Clipper.AddPolygon(subject, ptSubject);
  Clipper.AddPolygon(clip, ptClip);
  Clipper.Execute(ctIntersection, solution);
finally
  Clipper.Free;
end;
< p > finallyend 之间的代码保证会执行,即使出现异常,甚至在 tryfinally 之间调用 Exit

Mason 的意思是,有时候 with 结构可能会引起头疼,因为会出现标识符冲突。例如,请考虑:

MyObject.Caption := 'My test';

如果你在with结构中编写此内容,即如果你编写
with MyObect do
begin
  // A lot of code
  Caption := 'My test';
  // A lot of code
end;

如果你不熟悉编程,可能会感到困惑。实际上,大多数情况下Caption :=会更改当前窗体的标题,但是现在由于with语句,它将更改MyObject的标题。

更糟糕的是,如果

MyObject.Title := 'My test';

如果MyObject没有Caption属性,而你忘记了这一点(认为该属性被称为Caption),那么

MyObject.Caption := 'My test';

无法编译,而

with MyObect do
begin
  // A lot of code
  Caption := 'My test';
  // A lot of code
end;

我会编译,但是它不会按照你的期望工作。

此外,类似以下结构的代码

with MyObj1, MyObj2, ..., MyObjN do

或者像以下这样使用嵌套的with语句:

with MyConverter do
  with MyOptionsDialog do
    with MyConverterExtension do
      ..

我是一名帮助翻译的助手。以下是需要翻译的内容:

可以产生许多冲突。

With语句辩护

我注意到几乎有一个共识(至少在这个帖子中),即with语句比好处更多。虽然我意识到潜在的混淆,并且已经犯过几次错误,但我不能同意。仔细使用with语句可以使代码看起来更漂亮。这降低了由于"barfcode"而导致的混淆风险。

例如:

对比

var
  verdata: TVerInfo;

verdata := GetFileVerNumbers(FileName);
result := IntToStr(verdata.vMajor) + '.' + IntToStr(verdata.vMinor) + '.' + IntToStr(verdata.vRelease) + '.' + IntToStr(verdata.vBuild);

使用

with GetFileVerNumbers(FileName) do
  result := IntToStr(vMajor) + '.' + IntToStr(vMinor) + '.' + IntToStr(vRelease) + '.' + IntToStr(vBuild);

我完全没有混淆的风险,而且在最后一种情况下我们不仅保存了一个临时变量,而且代码更易读。

或者说这个非常标准的代码:

with TAboutDlg.Create(self) do
  try
    ShowModal;
  finally
    Free;
  end;

风险在哪里?从我的代码中,我可以提供数百个更多的with语句示例,所有这些都简化了代码。

此外,正如上面所述,只要你知道你在做什么,使用with就没有任何风险。但是,如果您想要在上面的示例中将with语句与MyObject一起使用:那么,在with语句内部,Caption等于MyObject.Caption。那么如何更改窗体的标题呢? 简单!

with MyObject do
begin
  Caption := 'This is the caption of MyObject.';
  Self.Caption := 'This is the caption of Form1 (say).';
end;

在处理需要执行时间较长的属性或函数结果时,with语句可以派上用场。

以上面的TClipper示例为例,假设您有一个TClipper对象列表,其中包含一个slow方法,该方法返回特定TabSheet的剪辑器。

理想情况下,您应该只调用此getter一次,因此您可以使用显式局部变量或使用with的隐式变量。

var
  Clipper : TClipper;
begin
  Clipper := ClipList.GetClipperForTab(TabSheet);
  Clipper.AddPolygon(subject, ptSubject);
  Clipper.AddPolygon(clip, ptClip);
  Clipper.Execute(ctIntersection, solution);
end;

或者

begin
  with ClipList.GetClipperForTab(TabSheet)do
  begin
    AddPolygon(subject, ptSubject);
    AddPolygon(clip, ptClip);
    Execute(ctIntersection, solution);
  end;
end;

在这种情况下,任何一种方法都可以,但在某些情况下,通常在复杂的条件语句中,使用 with 可能更清晰。

var
  Clipper : TClipper;
begin
  Clipper := ClipList.GetClipperForTab(TabSheet);
  if (Clipper.X = 0) and (Clipper.Height = 0) and .... then
    Clipper.AddPolygon(subject, ptSubject);
end;

begin
  with ClipList.GetClipperForTab(TabSheet) do
    if (X = 0) and (Height = 0) and .... then
      AddPolygon(subject, ptSubject);
end;

最后,这是个人品味的问题。通常我只会在非常紧密的范围内使用标签,并且从不嵌套使用。用这种方式,它们是减少混乱代码的有用工具。

5
在IDE能够确定"with"指的是什么之前,我会继续避免使用它。 - Chris Thornton
5
当然,你是指调试器而不是IDE。IDE本身对于with的含义没有问题。IDE的编译器部分可以完美准确地编译“with”代码,Code Insight也可以正确地解析引用。只有调试器无法正确理解它。 - Deltics
1
在我看来,with 的主要缺点是它会让我们把代码放到错误的地方。如果 with 语句中的代码被放置在为其提供别名的结构内部,那么可以实现相同的简洁性优势,并且调用者的代码看起来更加干净。 - Caleb Hattingh
@cjrh:嗯,我会说这只适用于不到50%的with使用情况,其中大部分是记录,将代码添加到记录本身是没有任何意义的。 - Andreas Rejbrand
向记录添加方法是状态和操作的捆绑。这与类的原因完全相同。如果您的代码段需要操纵记录中如此多不同的字段以证明with语句是合理的,那么我认为让记录通过方法调用对自身进行操作可能更好。 - Caleb Hattingh
显示剩余2条评论

15

这是对TObject.Free的调用,它基本上被定义为:

if self <> nil then
  self.Destroy;

这是在with语句中创建的未命名的TClipper对象上执行。

这是为什么你不应该使用with的很好的例子。它往往使代码更难读懂。


7
这是一个非常方便和标准的结构。这样,如果您只想在某个地方使用几种方法,就不需要用一长串的TOpenDialog、TMyConverter、TMyCustomDialog、TMySoundLibrary变量来启动您的过程/单元。 - Andreas Rejbrand
2
@mjustin:我理解可能会出现的(未预料到的)错误和对with语句的反对意见,但有些例子实际上可以提高代码的可读性并防止非常冗长的变量声明(如果我们可以像在C中一样随处声明变量那就太好了,尽管语言纯粹主义者可能也不喜欢这种做法)。 - Remko
2
代码非常容易阅读。发帖者不知道“Free”是什么,也不知道“with”的工作原理。消除“with”的使用只解决了理解差距的一半。 - Deltics
1
如果回答了<code>free</code>的作用,我会给+1分,但如果说这是不应该使用<code>with</code>的例子,我会给-1分。我唯一会使用“不应该”的情况是你不应该使用你没有完全理解的工具。 - macf00bar

7

Free函数调用对象的析构函数,并释放该对象所占用的内存。


3
我对Delphi一无所知,但我认为它会释放TClipper使用的资源,就像在C#中的using语句一样。这只是一个猜测...

0

任何动态创建的对象必须在使用后调用free以释放分配的内存。TClipper对象是一个桌面内容创建、捕获和管理工具。因此,它是一种带有Clipper的Delphi连接对象。 create(对象创建)在try finally end;语句中处理,这意味着如果与Clipper的连接不成功,则对象TClipper将不会被创建,并且不能在try finally end;语句之后释放。


0
如果“with”像一些帖子所暗示的那样邪恶,他们能否解释一下:
1. 为什么Borland创建了这种语言结构,以及
2. 为什么他们(Borland / Embarcadero / CodeGear)在自己的代码中广泛使用它?
虽然我当然理解一些Delphi程序员不喜欢“with”,并承认一些用户滥用它,但我认为说“你不应该使用它”是愚蠢的。
犯罪代码的作者angusj :)

2
Borland没有创建这个语言结构。他们是从Wirth那里继承来的。在Pascal->Object Pascal跨越之前,这并不是一个很大的问题,因为当时不存在被包含在方法内部的隐式self作用域。您可以将所有方法视为包装在一个with self do块中,一旦有了两个隐式作用域层,就可能遇到一些非常棘手的解析问题,引入错误并使代码更难以修复。 - Mason Wheeler
2
梅森,我完全同意“with”关键字经常被滥用到一定程度,以至于某些方法/属性/变量的所有者不清楚甚至具有误导性。然而,在非常小的代码块中使用时,没有歧义,并且在我看来,“with”实际上可以提高清晰度。 - Angus Johnson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接