【问题标题】:String lifetime management, in records字符串生命周期管理,在记录中
【发布时间】:2014-05-05 06:55:58
【问题描述】:

我正在努力摆脱短字符串。 目前在我们的程序中使用短字符串的许多地方之一是在记录中。 很多这些记录都保存在 AVL 树中。

使用的 AVL 树是一个通用的树,包含一个指向多个字节 (ElemSize) 的指针,到目前为止运行良好。 AVL 树中每条记录的内存使用GetMem 分配,并使用Move 进行复制。 但是,由于字符串是指向引用计数结构的指针,因此将内存复制回记录不再起作用,因为引用的字符串通常被释放(通过引用计数自动释放)。 只有一个指针和“数据块”的大小,我认为不可能增加字符串的引用计数。

我正在寻找一种方法来获取在将记录存储在 AVL 树中时要考虑的字符串的引用计数。

我可以将记录类型传递给树构造函数,然后将指针转换为这种类型,从而增加引用吗?或类似的修复,我可以将更改隔离为主要在 AVL 单元中并调用它的构造函数。

用于分配空间以存储 AVL 中记录的当前代码; XData 是指向要存储的记录的指针:

New(RootPtr); { create new memory space }
GetMem(RootPtr^.TreeData, ElemSize);
WITH RootPtr^ DO BEGIN
    { copy data }
    Move(XData^, RootPtr^.TreeData^, ElemSize);

【问题讨论】:

  • 实际上您需要进行适当的重新设计。你不能这样继续下去。
  • 为什么需要担心引用计数?当然,您在树节点中的记录将包含一个自动引用的string,最好的方法是摆脱为您的记录(例如New、GetMem)手动分配内存并使用新方法。我会让节点亲自存储对象引用。它可能比你目前正在做的内存移动更有效率。
  • @DavidM 1. 目前,树节点中没有记录,只有一个指针TreeData(它包含记录数据,但未声明为记录)。 2. 从树中取出的记录通常用于临时计算,这不应该反映在正确的记录中,因此我们需要在每次请求时将记录复制出来,而不是传递引用。
  • @DavidHeffernan 重新设计如何摆脱 AVL?还是远离记录?当我们的程序准备好超越 Delphi 2007 时,我希望切换到通用容器类。但现在还没有,而且随着升级的时间,我必须在很多旧代码之间优先考虑,试图超越 2007。
  • @MGH 我添加了一个答案,其中概述了我认为最简单的方法,让您在没有太多变化的情况下继续前进

标签: delphi record delphi-2007 reference-counting


【解决方案1】:

本质上你要问的问题是:

当我只知道它的类型是它的大小时,我如何分配、复制和解除分配记录?

简单的答案是您可以使用GetMem、Move 和FreeMem,前提是记录不包含托管类型。您希望使用包含托管的 Delphi 字符串的记录。所以你目前使用GetMem 和Move 的方法是不够的。

有很多方法可以解决这个问题。您可以编写自己的代码来进行引用计数,只要您知道托管类型在记录中的位置。我不推荐这个。您可以将您的用户数据设为一个类并使用多态性来提供帮助。

我想讨论的选项继续支持记录,并且确实允许用户选择他们喜欢的任何类型。推理如下:

如果该类型包含托管类型,则对其进行操作需要了解该类型。如果树是通用的,那么它就不能拥有那种知识。因此,知识必须由树的用户提供。

这会引导您参加活动。让树提供用户可以为其提供处理程序的事件。类型如下所示:

type
  PTreeNodeUserData = type Pointer;

  TTreeNodeCreateUserDataEvent = function: PTreeNodeUserData of object;
  TTreeNodeDestroyUserDataEvent = procedure(Data: PTreeNodeUserData) of object;
  TTreeNodeCopyUserDataEvent = procedure(Source, Dest: PTreeNodeUserData) of object;

然后您可以安排您的树以用户可以订阅的这些类型发布事件。

关键是这允许树的用户提供有关用户数据类型的缺失知识。

【讨论】:

    【解决方案2】:

    使用记录的主要好处之一是可以简单地复制它们(不使用Move)。因此,您最好的解决方案是简单地将Move 替换为普通的赋值运算符:=。这将正确考虑所涉及的所有托管类型的引用计数。

    您不使用普通赋值运算符是否有特殊原因?

    PS:您需要确保所有托管类型(包括长字符串)的内存都已正确初始化和最终确定。我建议您对Initialize 和Finalize 例程进行一些额外的阅读。


    树是通用的,它可以保存给定的数据块。我希望我可以在不为每条记录创建新树类的情况下扩展功能。

    在这种情况下,您的“复制行为”需要根据所使用的内容而变化。作为几个选项:

    1. 如果您的树被包装在一个类中,您可以轻松修改它以使用回调事件来执行复制操作。 (即使您首先必须将树封装在一个类中,此选项也可能是最简单的。)

    2. 将您的节点和/或数据修改为具有多态复制功能的对象。然后每个子类型都会知道如何正确复制自己,你可以按照Root.TreeData := XData.CreateCopy;

    3. 这样写一些东西

    【讨论】:

    • 这棵树是通用的,它可以保存给定的数据块。我希望我可以在不为每条记录创建新树类的情况下扩展功能。
    • @MGH 您绝对不需要新的树类/记录类型。您需要做的就是正确处理抽象和多态接缝。
    • 如果您的记录是可变大小的,您可以考虑使用指向此类 blob 的指针或TBytes,而不是“静态”数组。那么您的记录是固定大小的,可以简单地使用New 分配并使用Dispose 释放。这也将处理任何包含的字符串的引用计数。
    • @Craig 如果类型是记录,再多的多态也无济于事。它们不支持多态性。提问者绝对需要重新设计。
    • @David:答案的倒数第三行:“2. 修改你的节点和/或数据是objects”
    【解决方案3】:

    如果你工作在这么低的级别,并且不希望编译器帮助你,那么你需要使用 PChar 字符串而不是常规字符串。

    【讨论】:

    • 由于记录并不总是按预期管理常规字符串的引用计数,我建议使用 TInterfacedObject 类而不是记录或使用 PChar 而不是字符串。 stanleyxu2005.blogspot.com/2008/01/…
    • @stanley 引用计数工作正常且符合预期。您的博客文章中的问题是您的代码完全损坏了。它和问题中的问题相同。
    • @DavidHeffernan 许多书籍、教程都展示了以这种方式初始化记录(通常用于调用 Windows API),它误导了我很长时间以这种方式编写代码。为了写代码安全,我不喜欢在记录中使用常规字符串。
    • @stanley 你不能用托管类型做到这一点。没有任何官方文本这么说。
    猜你喜欢
    • 2012-11-24
    • 1970-01-01
    • 1970-01-01
    • 2015-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-15
    • 2015-01-22
    相关资源
    最近更新 更多