Delphi中接口的内存管理
我来自C#,正在努力学习delphi和内存管理 这场斗争的当前体现是,当我处理完这些物品后,我不知道如何正确处置它们。从阅读和我的实验来看,如果我有一个对象被转换为接口,那么我唯一的选择就是将引用设置为nil。 如果我去打电话 FreeAndNil() 我最终遭到访问违规,例如:Delphi中接口的内存管理,delphi,memory-management,interface,access-violation,Delphi,Memory Management,Interface,Access Violation,我来自C#,正在努力学习delphi和内存管理 这场斗争的当前体现是,当我处理完这些物品后,我不知道如何正确处置它们。从阅读和我的实验来看,如果我有一个对象被转换为接口,那么我唯一的选择就是将引用设置为nil。 如果我去打电话 FreeAndNil() 我最终遭到访问违规,例如: var foo: IFoo; begin foo := TFoo.Create(); FreeandNil(foo); end; 当然,我所需要做的就是改变foo:IFoo;tofoo:TFoo;它是快乐
var
foo: IFoo;
begin
foo := TFoo.Create();
FreeandNil(foo);
end;
当然,我所需要做的就是改变foo:IFoo;tofoo:TFoo;它是快乐的。或者简单地将指针设置为nil,而不是调用freeandNil
foo := nil;
所以,在一个层面上,我一点也不明白AV在哪里
在另一个层次上,我想编写代码,这样它就不需要知道它是一个接口还是一个对象。我希望能够以同样的方式编写所有的内存管理,但我似乎无法编写一个能够处理类或接口的方法。嗯,那不是真的,我确实有一些东西,但它太难看了,我不愿意贴出来
但我想我也应该问,其他人都在做什么?在精神上跟踪什么是接口,而不去理会那些指针?否则调用FreeAndNil
我第一次想把它实现为一个具体的类,但后来当我发现代码可以通过两种不同的方式实现时,我会回来把它改成一个接口。我不想通过代码来改变它处理引用的方式,这是我当时最不想做的事情
但为了便于讨论,我最好(几乎是唯一)的想法是这门课:
interface
type
TMemory = class(TObject)
class procedure Free(item: TObject); overload; static;
class procedure Free<T: IInterface>(item: T); overload; static;
end;
implementation
uses
System.SysUtils;
{ TMemory }
class procedure TMemory.Free(item: TObject);
begin
FreeandNil(item);
end;
class procedure TMemory.Free<T>(item: T);
begin
//don't do anything, it is up the caller to always nil after calling.
end;
测试代码:
procedure TDoSomething.MyWorker;
var
foo: IFoo;
fooAsClass: TFoo;
JustAnObject: TObject;
begin
foo := TFoo.Create();
fooAsClass := TFoo.Create();
JustAnObject := TObject.Create();
TMemory.Free(foo);
foo := nil;
TMemory.Free(fooAsClass);
fooAsClass := nil;
TMemory.Free(JustAnObject);
JustAnObject := nil;
end;
运行时没有泄漏或访问冲突。(使用MadExcept)
但我要向德尔福社区表示衷心的感谢。你们是最好的学习对象 如果我们通过接口变量访问某个对象,并不总是意味着当引用计数器降为零时该对象就被破坏了。例如,
TComponent
methods\u AddRef和_Release实现是“虚拟的”:没有实现引用计数,TComponent永远不会被销毁,因为接口变量超出范围
要按照我们对“真实”接口的期望进行操作,您的所有对象都应该是TInterfacedObject
的后代,或者您需要自己实现\u AddRef
/\u Release
是的,有两种不同的内存管理方法,它们通常共存于一个程序中,但只有当以两种方式处理同一对象时,才会出现混淆(和AV)。如果我们销毁了对象,只有在接口变量超出范围时,它们才会调用销毁对象的\u Release
方法,从而导致访问冲突。这是一项有风险的业务,尽管经过一定的关注是可行的
经典的Delphi组件没有引用计数,而是使用所有权的概念。每个组件都有一个所有者,其责任是在其自身被破坏时释放所有内存。所以每个组件都有一个所有者,但它也可能有很多指向其他组件的指针,比如工具栏上有ImageList
变量时。如果这样的组件被重新计数,它们将永远不会因为循环引用而被破坏,因此为了打破这个循环,您还需要不“计数”的“弱”引用。它们也在这里,但这是Delphi最近的特性
如果您的对象中存在某种层次结构,因此您知道“较大”的对象需要所有“较小”的对象才能运行,那么请使用这种好的旧方法,它非常简单,并且在Delphi中有很好的实现,即:您可以生成一个无泄漏的代码,无论在何处出现异常。有很多小事情,比如使用.Free
而不是.Destroy
,因为如果构造函数中发生异常,就会自动调用析构函数,等等。事实上,这是一个非常聪明的解决方案
只有当您不知道某个对象需要多长时间,并且没有合适的“所有者”时,我才会使用refcounted接口。我把扫描的图像保存到一个线程的文件中,然后转换成更小的图像显示在另一个线程的屏幕上。当所有这些都完成后,RAM中不再需要图像,图像可以被销毁,但我不知道哪一个先发生。在这种情况下,使用refcounting是最好的选择。访问冲突的原因是
FreeAndNil
采用非类型化参数,但希望它是一个对象。因此,该方法对对象进行操作
procedure FreeAndNil(var Obj);
var
Temp: TObject;
begin
Temp := TObject(Obj); //Obj must be a TObject otherwise all bets are off
Pointer(Obj) := nil; //Will throw an AV if memory violation is detected
Temp.Free; //Will throw an AV if memory violation is detected
end;
如果销毁以前已销毁或从未创建的对象,则可能会检测到上述内存冲突(注意:不保证)。如果Obj
完全不引用对象,而是引用其他对象(例如接口、记录、整数,因为它们不实现Free
,如果它们实现了,它的位置将与TObject.Free
)不同,那么它也可能被检测到
在另一个层次上,我想编写代码,这样它就不需要知道它是一个接口还是一个对象。我希望能够以同样的方式编写所有内存管理
这就像说你想用和淋浴完全一样的方式使用你的汽车。好吧,也许差别并没有那么大。但关键是接口和对象(以及记录)使用不同的内存管理范式。你不能用同样的方法管理他们的记忆
- 需要显式销毁对象。您可以使用所有权模型,但销毁仍然是一个明确的外部操作
- 接口被引用计数。编译器注入代码以跟踪引用(查看)底层实例的字段和变量的数量。通常,对象会被销毁
procedure FreeAndNil(var Obj); var Temp: TObject; begin Temp := TObject(Obj); //Obj must be a TObject otherwise all bets are off Pointer(Obj) := nil; //Will throw an AV if memory violation is detected Temp.Free; //Will throw an AV if memory violation is detected end;