Delphi内存管理指南

4

情况

我正在学习Marco Cantu的Delphi书,并且已经有Java和PHP的面向对象编程经验。为了更好地理解书中内容,我做了这个测试:

type
 TFraction = class
  private
   number: double;
   num, den: integer;
   fraction: string;
   function hcf(x: integer; y: integer): integer;
  public
   constructor Create(numerator: integer; denominator: integer); overload;
   constructor Create(value: string); overload;
   function getFraction: string;
 end;

这是一个非常简单的类,可以将十进制数转换为分数。我不包括定义构造函数和函数的其他部分,因为它们对我的问题没有用处。我使用以下方式创建对象。

var a: TFraction;
begin

 a := TFraction.Create(225, 35);
 ShowMessage(a.getFraction);
 //The output of ^ is 45/7
 a.Free;

end;

问题

根据我所学到的知识,我知道在使用完对象后必须要将其清除并且实际上我在使用 Free。通过这种方式,我释放了内存并避免了内存泄漏。

另外,我还看到我有可能覆盖一个 destructor。我不太理解 FreeDestroy 的行为。当我需要摆脱不再需要的对象时,我使用 Free。当我重写一个析构函数时,我可以释放对象并执行其他操作?

简而言之,什么时候使用 Free 是好的?我何时应该优先选择 Destroy?


抱歉,阿尔贝托,关于答案评论中正在进行的战争。这是一场已经持续了几十年的古老战争。但这是一个让你更多地了解手头主题的绝佳机会。 - Jerry Dodge
没关系,学习任何东西都有用的 :) - Alberto Rossi
2个回答

7
通常情况下,析构函数用于在对象销毁时执行一些否则不会自动执行的操作,例如释放在构造函数中初始化的内存。在您的示例中,没有必要重写析构函数,因为(假定)您没有创建任何需要手动销毁的内容。
此外,请记住,Destroy 不打算由您调用 - 无论是内部还是外部都不行。 Free 会自动处理这个问题 - 还需要进行一些额外的工作。 Free 检查对象是否为 nil,并且仅在不为 nil 时调用 Destroy
以这个为例:
type
  TMyObject = class(TObject)
  private
    FSomeOtherObject: TSomeOtherObject;
  public
    constructor Create;
    destructor Destroy; override;
  end;

constructor TMyObject.Create;
begin
  inherited;
  FSomeOtherObject:= TSomeOtherObject.Create;
end;

destructor TMyObject.Destroy;
begin
  FSomeOtherObject.Free;
  inherited;
end;

额外提醒一下,我发现上面的用法有所缺失。如果在CreateFree之间的代码引发异常呢?程序将退出,并且永远不会被释放。因此,应该使用try / finally块...

a := TFraction.Create(225, 35);
try 
  ShowMessage(a.getFraction);
finally
  a.Free;
end;

这将确保无论在tryfinally之间发生什么,finallyend之间的代码将始终被调用。

"Destroy 不应该被调用" 这是不正确的。 - Free Consulting
@FreeConsulting 你能详细阐述一下这个说法,并考虑回复Ken对Remy答案的评论吗?你能呼叫它吗?可以。是否有直接调用它的原因?我不知道。还是你只是像其他人一样在寻找证明我的话错误的理由? - Jerry Dodge
1
非常感谢。我已经阅读了关于内存管理的书的下一章,并找到了你告诉我的内容。我将使用Free,因为它会“决定”何时调用析构函数。谢谢 :) - Alberto Rossi
@Rudy 确实如此,即使是这样,只需要一个简单的 inherited; 就足够了。它只是简单地遵循继承类的要求。 - Jerry Dodge
@JerryDodge:正确。inherited就足够了,但是它确实调用了Destroy - Rudy Velthuis
显示剩余6条评论

3
顺便提一下,我也有可能覆盖一个析构函数。我不是很理解Free()Destroy()的行为。
当对象指针不为空时,Free()调用析构函数。 Destroy()实际上是析构函数。
当我需要摆脱一个我不再需要的对象时,我使用Free。当我覆盖析构函数时,我可以释放对象并执行其他操作吗?
可以。当对象正在被销毁时,析构函数会被调用。覆盖析构函数是执行与正在被销毁的对象相关的清理操作的好地方。
简而言之,什么时候使用Free?什么时候应该使用Destroy?
您可以直接调用Destroy(),但通常最好调用Free(),并让它代替您调用Destroy()

2
你能提供一个具体的例子,说明何时需要调用Destroy而不是Free吗?我无法回忆起曾经遇到过这种情况,而且我从来不会自己调用Destroy;我很想看看我所忽略的这种情况。 - Ken White
2
如果您确定某个对象指针绝不会为nil(例如包含在try/finally中的局部变量),则可以直接调用Destroy()。如果不确定,请改为调用Free()。在RTL/VCL源代码中,有一些(不多,注意)直接调用Destroy()的示例。例如,在TComponent.DestroyComponents()中。 - Remy Lebeau
2
Free()是为了避免构造函数异常而引入的,因为析构函数会自动调用。在进入构造函数之前,对象内存被清零,以确保任何未初始化的成员在析构函数中都将是零/nil。这样,析构函数编写者就不必跟踪哪些成员在异常之前已经初始化了。我并不是说不要使用Free()。我们大多数人(包括我自己)都会使用它。我只是想说,当您知道可以安全地这样做并且不需要检查nil时,可以直接调用Destroy() - Remy Lebeau
2
Allen Bauer在2006年的博客中谈到了这个话题:异常安全 - Remy Lebeau
1
我认为这里的争论源于“能”和“应该”的不同。我“可以”决定将伏特加倒入我的汽车燃油箱中。但是“应该”吗?出于安全起见,我认为不应该。 - Jerry Dodge
显示剩余6条评论

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