阻止客户端代码在Delphi中释放共享对象

时间:2011-05-04 02:49:09

标签: delphi

我在Delphi应用程序中实现了FlyWeight模式。一切都运作良好,一切都快得多,记忆力也减少了,但我担心的还有一件事。

只要客户端代码从不在共享对象上调用Free(),我的实现就会起作用。在Flyweight模式中,FlyweightFactory本身应该“维护对flyweights的引用”,即对共享对象。

我的问题是没有(明显的)方法阻止其他代码在引用后销毁它们。我可以忍受这个,但如果我可以自由地绕过这些物体而不用担心意外的自由,那将是一个“大胜利”。

显示一个(人为的)示例:

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

我考虑重写析构函数as shown here以完全释放flyweight对象。在我的情况下,这不是一个选项

a)我只想阻止缓存的对象被释放,而不是不属于缓存的对象。有很多遗留代码不使用缓存;他们仍然需要手动创建和释放对象。

b)我希望FlyweightFactory在完成期间释放对象;我同意Warren P认为“零泄露记忆”政策是最好的。

我将从GoF的Flyweight章节中引用

  

可用性意味着某种形式的   引用计数或垃圾   收集回收存储时   它不再需要了。然而,   如果数量不合适,则不需要   flyweights固定而小。在那里面   情况下,飞锤值得保留   永久地。

在我的情况下,飞重是“固定的”和(足够)小。

[更新有关我如何解决此问题的详细信息,请参阅我的回答]

5 个答案:

答案 0 :(得分:2)

My answer的{p> question you link to仍然适用。对象必须通过私有布尔标志知道它们是缓存对象。然后他们可以选择不在DestroyFreeInstance中摧毁自己。如果你想允许调用Free,那就没有其他选择。

要处理完成,您需要将缓存的对象添加到缓存对象列表中。可以在最终确定时释放该对象列表。当然,当您走过列表时,必须重置禁用释放的标志。

关于最终确定,我建议你注册预期的内存泄漏并泄漏这个内存。它使代码更简单,没有什么可失去的。一旦可执行文件关闭,操作系统将回收您没有释放的任何内存。需要注意的一点是:如果您的代码被编译成DLL,那么如果您的DLL被加载,卸载,再次加载等等,则泄漏可能会很麻烦。

这一切告诉你的是你正在逆流而行。您是否有可能通过不同的解决方案实现您的目标,这种解决方案更适合Delphi引导您的方式?

答案 1 :(得分:0)

我建议添加引用计数,以便知道您的共享对象是否仍在使用。 每个客户端都应使用AddRef / Release模式(AddRef增加计数; Release减少;如果count达到零,则调用Free)

可以通过GetFlyweight方法直接调用AddRef;必须使用Release而不是Free。

如果你重构你的类并从中提取一个接口,那么在接口实现中自然实现AddRef / Release模式。 (您可以从TInterfacedObject派生或通过自己实现IInterface)

答案 2 :(得分:0)

理想情况下,您很少需要两种方法来使用相同的东西。从长远来看,这只会使问题复杂化。在6个月的时间内,您可能无法确定某段代码是使用新的轻量级范例还是旧范例。

阻止某人致电FreeDestroy的最佳方法是确保不存在。在Delphi世界中,唯一的方法是使用interfaces

扩展你人为的例子:

type
  TFlyweightObject = class
  public
    constructor Create(ANumber: Integer);
    function Description: string;
  end;

  TFlyweightFactory = class
  public
    function GetFlyweight(ANumber: Integer): TFlyweightObject;
  end;

这个对象很容易被流氓客户所困扰。您可以进行以下更改:

type
  IFlyweight = interface
  //place guid here
    function Description: string;
  end;

  TFlyweightObject = class(TInterfacedObject, IFlyweight)
  public
    constructor Create(ANumber: Integer);
    function Description: string;
  end;

  TFlyweightFactory = class
  public
    function GetFlyweight(ANumber: Integer): IFlyweight;
  end;

现在任何更新为使用flyweight范例的代码都被迫按预期使用它。它还更容易识别仍需要重构的旧代码,因为它不使用接口。旧代码仍然会直接构造“flyweight”对象。

答案 3 :(得分:0)

我设法通过David Heffernan在他的回答中提出的以下技术解决了我在原始问题中引用的问题。

  

a)我只想停止缓存的对象   从被释放,而不是对象   不是缓存的一部分。有一个   很多遗留代码都没有使用   缓存;他们仍然需要创造   和手动免费对象。

我通过继承Flyweight类并仅在子类中覆盖destroy,BeforeDestruction和FreeInstance来修复此问题。这使父类保持原样。缓存包含子类的实例(无法释放),而缓存外的对象可以按常规释放。

  

b)我确实想要FlyweightFactory   在完成期间释放对象;   我同意Warren P认为"零   泄露记忆"政策是最好的。

为了解决这个问题,我添加了一个私有布尔标志,在释放对象之前必须将其设置为true。此标志只能从缓存单元设置,对其他代码不可见。这意味着无法通过缓存外部的代码将标志设置在外部。

析构函数看起来像这样:

destructor TCachedItem.destroy;
begin
    if destroyAllowed then
        inherited;
end;

如果客户端代码尝试释放缓存对象,则该调用将不起作用。

答案 4 :(得分:0)

您还可以通过将析构函数设为protectedprivate来隐藏它。程序员不会在声明它的单元范围之外看到它。

但我发布此答案更像是一种好奇心,因为这不会阻止使用FreeAndNil或使用"Protected Hack"

释放对象