Правильное использование 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);
конец;