我正在努力学习来自C#的delphi和内存管理。
目前这种斗争的化身是我不知道在完成这些物品时处理物品的正确方法。从阅读和我的实验看来,如果我有一个被转换为接口的对象,那么我的唯一选择是将引用设置为nil。 如果我去打电话 FreeAndNil() 我最终获得了访问冲突,EX:
var
foo: IFoo;
begin
foo := TFoo.Create();
FreeandNil(foo);
end;
当然,我需要做的就是改变那个foo:IFoo; foo: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;
然后我可以一直打电话:
TMemory.Free(Thing);
Thing := nil;
测试代码:
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)
但非常感谢Delphi社区的SO。你们这是学习中最好的东西!
答案 0 :(得分:7)
访问冲突的原因是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;
如果您销毁先前已销毁或从未创建过的对象,则会检测到上述中的内存冲突,可能(NB无法保证)。如果Obj
根本没有引用某个对象,也可能检测到其他东西(例如接口,记录,整数,因为它们没有实现Free
,如果它们没有实现,它就会被检测到与TObject.Free
)的定位方式相同。
在不同的层面上,我想编写代码,使得它不需要知道它是接口还是对象。我希望能够以同样的方式编写所有内存管理。
这就像是说你要用的方式与你使用淋浴的方式完全一样 好吧,也许差异并不是那么极端。但关键是接口和对象(以及相关记录)使用 不同的 内存管理范式。 不能 以同样的方式管理他们的记忆。
答案 1 :(得分:3)
如果我们通过接口变量访问某个对象,它并不总是意味着在参考计数器降至零的那一刻就破坏了该对象。例如,TComponent
方法_AddRef和_Release实现是&#39; dummy&#39;:没有实现引用计数,并且TComponent永远不会被销毁,因为接口变量超出了范围。
表现得像我们期望的那样真实&#39;接口,您的所有对象都应该是TInterfacedObject
的后代,或者您需要自己实现_AddRef
/ _Release
。
是的,有两种不同的内存管理方法,它们通常在一个程序中共存,但只有在同时处理同一个对象时才会产生混淆(和AV)。如果我们销毁了对象并且只有接口变量超出了范围,它们会调用被破坏对象的_Release
方法,从而导致访问冲突。这是一项有风险的业务,虽然有一些注意力是可行的。
Classic Delphi组件不是引用计数,而是使用所有权的概念。每个组件都有一个所有者,其职责是在销毁本身时释放所有内存。因此每个组件都有一个所有者,但它也可能有很多指向其他组件的指针,例如当工具栏有ImageList
变量时。如果这些组件被重新安装,它们永远不会因为循环引用而被破坏,所以为了打破这个循环,你需要“弱”。参考文献以及不计数的参考文献。他们也在这里,但这是Delphi最近的特色。
如果您的对象中存在某种层次结构,那么您就知道“更大”的层次结构。物品需要所有“小”的物品。要运行,然后使用这个好的旧方法,它非常简单,并且在Delphi中具有非常好的实现,即:无论出现什么异常,您都可以创建无泄漏的代码。有一些小问题,比如使用.Free
而不是.Destroy
,因为如果在构造函数中发生异常,则会自动调用析构函数,依此类推。事实上非常聪明的解决方案。
只有在您不知道某个对象需要多长时间并且没有合适的“所有者”时,我才会使用refcounted接口。为了它。我用扫描图像做了它,我在一个线程中保存到文件,同时转换为较小的图像在另一个线程的屏幕上显示。当一切都完成后,RAM中不再需要图像并且可以将其销毁,但我不知道哪个会先发生。在这种情况下,使用引用计数是最好的事情。