阻止客户端代码释放 Delphi 中的共享对象
我已经在我的 Delphi 应用程序中实现了 FlyWeight 模式。一切都很顺利,一切都快得多,占用的内存也更少,但有一件事我担心。
仅当客户端代码从不对共享对象调用 Free() 时,我的实现才有效。在 Flyweight 模式中,FlyweightFactory 本身应该“维护对 Flyweight 的引用”,即对共享对象的引用。
我的问题是,一旦其他代码拥有引用,就没有(明显的)方法可以阻止其他代码销毁对象。我可以忍受这个,但如果我可以自由地传递这些物体而不用担心意外释放,那将是一个“巨大的胜利”。
显示一个(人为的)示例:
flyweight1:=FlyweightFactory.GetFlyweight(42);
WriteLn('Description is '+flyweight.Description);
flyweight1.Free;
flyweight2:=FlyweightFactory.GetFlyweight(42);
WriteLn('Description is '+flyweight.Description);
// Object has already been Freed!; behaviour is undefined
我考虑过重写析构函数 如此处所示 以停止完全释放享元对象。在我的情况下,这不是一个选项,因为
a) 我只想阻止释放缓存的对象,而不是不属于缓存的对象。有很多遗留代码没有使用缓存;他们仍然需要手动创建和释放对象。
b) 我确实希望 FlyweightFactory 在终结期间释放对象;我同意 Warren P 的观点,即“零泄漏内存”政策是最好的。
我将引用 GoF 的 Flyweight 章节中的话结束
可共享性意味着某种形式的 引用计数或垃圾 收集以回收存储 不再需要它了。然而, 如果数量 Flyweights 是固定的并且很小。在那 这种情况下,蝇量级值得保留 永久存在。
就我而言,蝇量级是“固定的”并且(足够)小。
[更新请参阅我的答案以了解我如何解决此问题的详细信息]
I have implemented the FlyWeight pattern in my Delphi application. Everything has worked great, everything is a lot faster and takes less memory, but there is one thing I am worried about.
My implementation will only work as long as client code never calls Free() on the shared objects. In the Flyweight pattern, the FlyweightFactory itself is supposed to "maintain a reference to flyweights" i.e. to the shared objects.
My problem is that there is no (obvious) way to stop other code from destroying the objects once they have a reference. I could live with this, but it would be a "big win" if I could pass these objects round freely without worrying about accidental freeing.
To show a (contrived) example:
flyweight1:=FlyweightFactory.GetFlyweight(42);
WriteLn('Description is '+flyweight.Description);
flyweight1.Free;
flyweight2:=FlyweightFactory.GetFlyweight(42);
WriteLn('Description is '+flyweight.Description);
// Object has already been Freed!; behaviour is undefined
I have considered overriding the destructor as shown here to stop the flyweight object being freed altogether. This is not an option in my case as
a) I only want to stop cached objects from being Freed, not objects that aren't part of the cache. There is a lot of legacy code that doesn't use the cache; they still need to create and free objects manually.
b) I do want the FlyweightFactory to Free the objects during finalization; I agree with Warren P that a "zero leaked memory" policy is best.
I'll leave with a quote from the Flyweight chapter of GoF
Sharability implies some form of
reference counting or garbage
collection to reclaim storage when
it's no longer needed. However,
neither is necessary if the number of
flyweights is fixed and small. In that
case, the flyweights are worth keeping
around permanently.
In my case the flyweights are "fixed" and (sufficiently) small.
[UPDATE See my answer for details of how I solved this problem]
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我的回答 您链接到的问题仍然适用。这些对象必须通过私有布尔标志知道它们是缓存的对象。然后他们可以选择在
Destroy
和FreeInstance
中不销毁自己。如果您想允许调用Free
,确实别无选择。为了处理终结,您需要将缓存对象添加到缓存对象列表中。该对象列表可以在最终确定时释放。当然,当您遍历列表时,必须重置禁用释放的标志。
就终结而言,我建议您注册预期的内存泄漏并泄漏该内存。它使代码变得更加简单,并且没有什么可丢失的。一旦可执行文件关闭,操作系统将立即回收任何未释放的内存。需要注意的是:如果你的代码被编译成 DLL,那么如果你的 DLL 被加载、卸载、再次加载等,泄漏可能会很麻烦。
所有这些都告诉你,你正在逆流而行。您是否可以通过更适合德尔福引导您的方式的不同解决方案来实现您的目标?
My answer to the question you link to still applies. The objects must know by means of a private boolean flag that they are cached objects. Then they can elect not to destroy themselves in
Destroy
andFreeInstance
. There really is no alternative if you want to allowFree
to be called.To deal with finalization you would want to add the cached objects to a list of cached objects. That list of objects can be freed at finalization time. Of course the flag to disable freeing would have to be reset whilst you walked the list.
Having made this point regarding finalization, I would advise you to register an expected memory leak and just leak this memory. It makes the code much simpler and there's nothing to lose. Any memory you don't free will be reclaimed by the OS as soon as your executable closes. One word of caution: if your code is compiled into a DLL then leaking could be troublesome if your DLL is loaded, unloaded, loaded again etc.
What all this is telling you is that you are swimming against the current. Is it possible that you could achieve your goals with a different solution that fitted better with the way Delphi is steering you?
我建议添加引用计数,以便了解您的共享对象是否仍在使用。
每个客户端都应该使用 AddRef / Release 模式(AddRef 增加计数;Release 减少计数;如果计数达到零,则调用 Free)。
AddRef 可以直接由 GetFlyweight 方法调用;必须使用 Release 而不是 Free。
如果重构类并从中提取接口,则 AddRef/Release 模式自然会在接口实现中实现。 (您可以从 TInterfacedObject 派生或自行实现 IInterface)
I suggest to add a reference count in order to known if your shared object is still used.
Every client should use the pattern AddRef / Release (AddRef increases the count; Release decrements it; if count reaches zero Free is called)
The AddRef may be called directly by your GetFlyweight method; Release has to be used instead of Free.
If you refactor your class and extract an interface from it the AddRef/Release pattern in naturally implemented in then interface implementation. (You could derive from TInterfacedObject or implement IInterface by your self)
理想情况下,您很少需要两种方法来使用相同的东西。从长远来看,这只会让事情变得复杂。 6 个月后,您可能不确定某段特定代码是使用新的享元范式还是旧范式。
防止有人调用
Free
或Destroy
的最好方法是确保它不存在。在 Delphi 世界中,实现这一点的唯一方法是使用接口
。扩展您设计的示例:
这是一个对象,很容易被流氓客户端摧毁。您可以进行以下更改:
现在,任何更新为使用享元范例的代码都被迫按预期使用它。识别仍需要重构的旧代码也更容易,因为它不使用接口。旧代码仍然会直接构造“享元”对象。
Ideally you seldom want 2 ways of using the same things. It just complicates matters in the long run. In 6 months time, you might not be sure whether a particular piece of code is using the new flyweight paradigm or the old paradigm.
The best way to prevent someone calling
Free
orDestroy
is to make sure it's not even there. And within the Delphi world, the only way to do that is to useinterfaces
.To expand on your contrived example:
This being an object can easily be destoyed by a rogue client. You could make the following changes:
Now any code that is updated to use the flyweight paradigm is forced to use it as intended. It's also easier to recognise the old code that still needs to be refactored because it doesn't use the interface. Old code would still construct the "flyweight" object directly.
您还可以通过将析构函数设为
受保护
或私有
来隐藏它。程序员不会在声明它的单元范围之外看到它。但我发布这个答案更像是出于好奇,因为这不会阻止使用
FreeAndNil
或使用“受保护的黑客”You could also hide a destructor by making it
protected
orprivate
. Programmers won't see it outside the scope of the unit in which it is declared in.But I am posting this answer more like a curiosity because this will not prevent freeing an object by using
FreeAndNil
or by using a "Protected Hack"我设法使用大卫·赫弗南(David Heffernan)在回答中建议的以下技术解决了我在原始问题中引用的问题。
我通过子类化 Flyweight 类并仅在子类中重写 destroy、BeforeDestruction 和 FreeInstance 来修复此问题。这使得父类保持原样。缓存包含子类的实例(无法释放),而缓存外部的对象可以照常释放。
为了解决这个问题,我添加了一个私有布尔标志,在释放对象之前必须将其设置为 true。该标志只能从缓存单元设置,对其他代码不可见。这意味着不能通过缓存外部的代码在外部设置该标志。
析构函数如下所示:
如果客户端代码尝试释放缓存的对象,则调用将无效。
I managed to get around the problems I cited in my original question using the following techniques, suggested by David Heffernan in his answer.
I fixed this by subclassing the Flyweight class and overriding destroy, BeforeDestruction and FreeInstance in the subclass only. This left the parent class as is. The cache contains instances of the subclass (which can't be freed), whereas objects outside the cache can be freed as per usual.
To solve this, I added a private boolean flag that has to be set to true before the object can be freed. This flag can only be set from the Cache Unit, it is not visible to other code. This means that the flag cannot be set outside by code outside the cache.
The destructor just looks like this:
If client code trys to Free a cached object, the call will have no effect.