Правильное использование Delphi XE TBytes

Каков правильный шаблон использования для переменной 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;

Спасибо заранее!

Ответы

Ответ 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 - это поле, поскольку оно используется только локально для этой функции. Это будет иметь больше смысла в качестве локальной переменной. Предположительно, есть причина, по которой он объявлен как поле, но с этим кодом его легко отличить.


Вы об использовании шаблона производителя/потребителя. Обычно это делается для того, чтобы отделить производство от потребления. Однако ваш примерный код не делает этого, по-видимому, потому что развязанный код будет слишком сложным, чтобы включить сюда.

Для настоящей реализации производителя/потребителя вам необходимо передать право собственности на данные от производителя на потребителя. Из того, что мы описали выше, очень простой и эффективный способ сделать это - использовать подсчет ссылок. Когда данные передаются потребителю, производитель должен отпустить свою ссылку на него.

Ответ 2

В вашем коде есть несколько злоупотреблений. Правильнее было бы следующее:

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;

Ответ 3

Переместить (p ^, FData, n); Это нормально

процедура TClassB.ProcessData(d: TBytes);//d - счетчик ссылок FData начать //d не содержит ничего, кроме FData, как и прежде, с refcount = 1 // если вы ставите ключевое слово "var" перед d, FData будет выпущен SetLength (d, 0);
конец;