【问题标题】:Delphi XE TBytes correct usageDelphi XE TBytes 正确用法
【发布时间】:2011-11-21 13:55:57
【问题描述】:

TBytes 变量的正确使用模式是什么?据我了解,TBytes 不是一个类,而是一个“动态字节数组”。我不确定在哪里分配内存,何时释放,以及将其从生产者传递给消费者的最佳方式。我希望我的生产者创建一个 TBytes 实例,然后将其传递给消费者。发生这种情况后,生产者希望重用其 TBytes 成员变量,内容是知道消费者最终会将内存返回给系统。如果 TBytes 是一个对象,我不会有任何问题,但我不确定 TBytes 在这种情况下是如何工作的。

例如,在对象 A 中,我想将一些数据组装成一个 TBytes 数组,该数组是对象 A 的成员。完成后,我想将 TBytes 数组传递给另一个对象 B,然后它就变成了数据的所有者。同时,回到对象 A,我想开始组装更多数据,重用 TBytes 成员变量。

type
  TClassA = class
  private
    FData: TBytes;
  public
    procedure AssembleInput(p: Pointer; n: Cardinal);
  end;

  TClassB = class
  public
    procedure ProcessData(d: TBytes);
  end;

var
  a: TClassA;
  b: TClassB;

procedure TClassA.AssembleInput(p: Pointer; n: Cardinal);
begin
  SetLength(FData, n);
  Move(p^, FData, n);  // Is this correct?
  ...
  b.ProcessData(FData);

  ...

  // Would it be legal to reuse FData now?  Perhaps by copying new (different)
  // data into it?
end;

procedure TClassB.ProcessData(d: TBytes);
begin
  // B used the TBytes here.  How does it free them?
  SetLength(d, 0);  // Does this free any dynamic memory behind the scenes?
end;

提前致谢!

【问题讨论】:

    标签: delphi delphi-xe


    【解决方案1】:

    Delphi 动态数组是具有自动生命周期管理的托管类型。它们是引用计数的,当引用计数变为 0 时,就会被处理掉。你可以认为它们在字符串、接口和变体方面是等价的。

    您可以通过以下三种方式之一显式释放对动态数组的引用:

    a := nil;
    Finalize(a);
    SetLength(a, 0);
    

    然而,当变量离开作用域时,什么都不做,让引用被释放是很常见的。

    使用动态数组需要注意的一点是,当您有两个对同一个动态数组的引用时。在这种情况下,通过一个引用应用的更改从另一个引用可见,因为只有一个对象。

    SetLength(a, 1);
    a[0] := 42;
    b := a;
    b[0] := 666;//now a[0]=666
    

    你问这是否正确:

    Move(p^, FData, n);
    

    不,不是。您在这里所做的是将p 的内容复制到引用FData 上。如果你想用Move复制,那么你可以写:

    Move(p^, Pointer(FData)^, n);
    

    或者,如果您希望更冗长一些并避免使用演员表,您可以编写:

    if n>0 then 
      Move(p^, FData[0], n);
    

    我个人并不觉得演员阵容太糟糕,因为Move 绝对没有类型安全。


    现在重用 FData 是否合法?也许通过将新的(不同的)数据复制到其中?

    如果没有更多上下文,我觉得我无法回答这个问题。例如,我不知道为什么 FData 是一个字段,因为它仅在该函数本地使用。作为局部变量会更有意义。大概是有原因的,它被声明为一个字段,但从这段代码中不能很容易地辨别出来。


    关于使用生产者/消费者模式。通常这样做是为了使生产与消费脱钩。但是,您的示例代码并没有这样做,大概是因为解耦代码太复杂而无法包含在此处。

    对于真正的生产者/消费者实施,您需要将数据的所有权从生产者转移给消费者。根据我们上面的描述,一个非常简单有效的方法是使用引用计数。当数据传输给消费者时,生产者应该释放对它的引用。

    【讨论】:

    • 感谢您的解释。是的,FData之所以是成员变量,是因为producer是从一个TCP socket中获取数据的,所以一般每次调用它都只获取部分数据。是的,我确实想要生产者/消费者,而关于“需要转移数据所有权”的评论正是我想要做的。是的,解耦的代码太复杂了。因此简化。最后,我仍然不确定如何正确“转让所有权”——你能澄清一下这个问题吗?
    • 让消费者拿一个引用,然后释放生产者的引用。 Consumer.data := FData; FData := nil;
    【解决方案2】:

    移动(p^, FData, n);没关系

    过程 TClassB.ProcessData(d: TBytes); // d 是 FData 的引用计数 开始 // d 什么都没有,但 FData 和以前一样,refcount = 1 // 如果在 d 前面加上 "var" 关键字,FData 将被释放 SetLength(d, 0);
    结束;

    【讨论】:

    • 不,不行。它将字节从 p 中的地址移动到 FData 中的指针。它应该是 FData[0]。
    【解决方案3】:

    您的代码中有几个误用。以下会更正确:

    type
      TClassA = class
      private
        FData: TBytes;
      public
        procedure AssembleInput(p: Pointer; n: NativeUInt);
      end;
    
      TClassB = class
      public
        procedure ProcessData(var d: TBytes);
      end;
    
    var
      a: TClassA;
      b: TClassB;
    
    procedure TClassA.AssembleInput(p: Pointer; n: NativeUInt);
    begin
      SetLength(FData, n);
      if n <> 0 then Move(p^, FData[0], n);
      ...
      b.ProcessData(FData);
      // FData is ready for reuse here...
    end;
    
    procedure TClassB.ProcessData(var d: TBytes);
    begin
      ...
      SetLength(d, 0);
    end;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-27
      • 2014-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多