【delphi】对象内存管理提示

Delphi 中的内存管理遵循两条简单规则:必须销毁自己创建和分配的每个对象和内存块,而且必须销毁每个对象并释放每个内存块仅一次。Delphi支持三个动态元素内存管理(即不在堆栈和全局内存区域),下面分别介绍:

  • 每次创建对象时,用完就应该释放它。如果未能做到这一点,那么该对象使用的内存不会释放给其他对象(一直占用内存),直到程序终止运行了。

  • ·创建组件时,可以指定组件的所有者(拥有者),并传递组件构造函数的所有者。所有者组件(通常是表单或数据模块)负责销毁其拥有的所有对象。换句话说,当您释放表单或数据模块时(所有者组件),它会释放它所包含的所有组件。所以,如果你创建一个组件并给它一个所有者,你就不需担心自己再去销毁(所有者会自动销毁)。

  • 为字符串、动态数组和引用的对象分配内存时通过接口变量,Delphi 自动释放引用超出范围时的内存。你不需要释放一个字符串:当它变得不可访问时,它的内存会被自动释放。

销毁自己创建的对象

比较简单的情况下,假如我们在桌面编译器上创建临时对象,就必须销毁它。任何非临时对象都应该有所有者,所有者要么可以收集,或者可以访问一些数据结构,总之所有者有理由和条件在适当的时候销毁它。用于创建和销毁临时对象的代码通常封装在try finally代码块中,这样即使出现问题,对象也会被最后销毁:

MyObj := TMyClass.Create;
try
  MyObj.DoSomething;
finally
  MyObj.Free;
end;

另一种常见的情况是,一个对象被另一个对象使用,这将成为其所有者:

constructor TMyOwner.Create;
begin
  FSubObj := TSubObject.Create;
end;
destructor TMyOnwer.Destroy;
begin
  FSubObj.Free;
end;

还有一些常见的更复杂的场景,就是对象延迟创建(在需要时才创建)或者在所有者销毁前就销毁。例如:

function TMyOwner.GetSubObject: TSubObject
begin
  if not Assigned (FSubObj) then
     FSubObj := TSubObject.Create;
  Result := FSubObj;
end;
destructor TMyOnwer.Destroy;
begin
  FSubObj.Free;
end;

请注意,在释放对象之前,不需要测试对象是否已指定,因为这正是Free所做的,下一节中我们将讨论。

对象只需要销毁一次

另一个问题是,如果调用一个对象的析构函数两次,就会得到一个错误。析构函数(destructor)的作用是用来释放对象内存的。可以在析构函数中增加一些我们自己的代码,以便对象在销毁前执行我们的程序。

Destroy是TObject(对象)类的虚拟析构函数。大多数时候,需要我们书写自己的清理代码来覆盖此虚拟方法。永远不要定义新的析构函数,因为对象是通过调用Free方法销毁,该方法调Destroy virtual析构函数(但是可以重写版本)。

正如前面提到的,Free只是TObject类的一种方法,由其他类继承的。Free方法在调用销毁虚拟析构函数(Destroy) 会检查当前对象(Self)是否为nil。

注释

您可能有疑问,如果对象引用为nil,为什么可以安全地调用Free,但不能调用摧毁(Destroy)原因?因为Free是给定内存位置的已知方法,而虚拟函数销毁(Destroy)是在运行时通过查看对象的类型来确定的,这是一个比较复杂的过程。如果对象都不存在了,再来销毁(Destroy),那就危险了(会出现错误)。

代码示例如下:

procedure TObject.Free;
begin
  if Self <> nil then
     Destroy;
end;

接下来,我们可以将讨论 Assigned 函数功能。当我们将指针传递到这个函数只测试指针是否为你nil。以下两种状态是相同的,至少在大多数情况下,这些条件是相当的:

if Assigned (MyObj) then
...
if MyObj <> nil then
...

注意,这些语句只测试指针是否为nil;并没有检查它是否是有效的指针。如果您编写以下代码:

MyObj.Free;
if MyObj <> nil then
   MyObj.DoSomething;

测试结果将为True,那么将调用对象的方法(DoSomething)。需要注意的是,Free 只是释放了对象,并没有把对象代表指针(MyObj)设置成nil,所以执行将会出错。 

自动设置对象指针(引用)为nil是不可能的。因为同一个对象可能有好几个引用,delphi并不跟踪这些引用。同时,在对象的方法中(例如Free)可以操作对象,但是并不知道有多少对象引用,也就是我们调用方法的对象内存地址。

换句话说,在Free方法或类的任何其他方法中,我们知道对象(self)的内存地址,但我们不知道对象的内存位置引用对象的变量,例如MyObj。所以,Free方法不能影响MyObj变量。

(结论:Free 方法可以释放对象,但是不能影响对象自身指针位置)

当然,我们可以通过调用外部函数,把对象作为参数传递,这样函数将能修改参数原有的值。这个典型的例子就是 FreeAndNil 过程函数。代码如下:

procedure FreeAndNil( const [ref] Obj: TObject); inline;
var
  Temp: TObject;
begin
  Temp := Obj;
  TObject(Pointer(@Obj)^) := nil;
  Temp.Free;
end;

在 Delphi 10.4 以前,FreeAndNil 参数就是一个普通的指针,你传递的参数可能是一个普通的指针、或者是一个接口引用、或者是一个不兼容的数据结构,这样调用 FreeAndNil很容易出现错误并且很难查找bug。但是10.4之后,FreeAndNil 的参数限制为一个常量的对象,只能是对象,就没有问题了。

备注:

不少 Delphi 专家会争辩说,FreeAndNil 永远不应该被使用。因为一个对象变量的可见引用应该匹配他的生命周期。 如果一个对象拥有另外一个对象,同时在destructor中释放了拥有的对象,就没有必要设置被引用对象为nil了,因为再也不需要了。同时如果使用 try finally 代码块释放了对象,也没必要将其设置成nil了。

另外,除了Free方法之外,TObject还有一个DisposeOf方法,该方法是Delphi几年来对ARC支持的遗留。目前DisposeOf方法只是调用Free方法。

总结一下这些清理对象内存操作的使用,这里有几个指导方针:

  • 总是调用对象的Free来销毁对象,而不是调用destroy destructor。
  • 使用FreeAndNil,或者在调用Free后将对象引用设置为nil,除非这个对象在Free之后再也不用了(自己需要明确知道不用了)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海纳老吴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值