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



delphi-xe (3)

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

Заранее спасибо!


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

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

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


Answer #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;

Answer #3

Переместить (p ^, FData, n); Хорошо

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





delphi-xe