Delphi XE TBytes правильное использование - PullRequest
17 голосов
/ 21 сентября 2011

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

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

Ответы [ 3 ]

15 голосов
/ 21 сентября 2011

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


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

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

4 голосов
/ 21 сентября 2011

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

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;
0 голосов
/ 21 сентября 2011

Move (p ^, FData, n);Это нормально

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

...