tokyo - embarcadero delphi
Delphi XE TBytes uso correcto (3)
¿Cuál es el patrón de uso correcto para una variable TBytes? Desde mi punto de vista, TBytes no es una clase, sino una "matriz dinámica de bytes". No estoy seguro de dónde se asigna la memoria, cuándo se libera y cuál es la mejor manera de pasarlo de un productor a un consumidor. Quiero que mi productor cree una instancia TBytes y luego la pase a un consumidor. Después de que esto suceda, el productor desea reutilizar su variable miembro de TBytes, con el conocimiento de que el consumidor eventualmente devolverá la memoria al sistema. Si TBytes fuera un objeto, no tendría ningún problema, pero no estoy seguro de cómo funcionan los TBytes en este escenario.
Por ejemplo, en el objeto A, deseo ensamblar algunos datos en una matriz TBytes que sea miembro del objeto A. Cuando eso se complete, quiero pasar la matriz TBytes a otro objeto B, que luego se convierte en el propietario de la matriz TBytes. datos. Mientras tanto, de vuelta en el objeto A, quiero comenzar a reunir más datos, reutilizando la variable miembro de 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;
¡Gracias por adelantado!
Hay un par de usos incorrectos en tu código. Lo siguiente sería más correcto:
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;
Los arreglos dinámicos de Delphi son tipos administrados que tienen administración automática de por vida. Se cuentan como referencias y cuando el recuento de referencias va a 0, se eliminan. Puede pensar que son equivalentes en cuanto a cadenas, interfaces y variantes.
Puede liberar explícitamente una referencia a una matriz dinámica de una de tres maneras:
a := nil;
Finalize(a);
SetLength(a, 0);
Sin embargo, es muy común simplemente no hacer nada y dejar que la referencia se libere cuando la variable abandona el alcance.
Una cosa a tener en cuenta con las matrices dinámicas es cuando tiene dos referencias a la misma matriz dinámica. En esa situación, los cambios aplicados a través de una referencia son visibles desde la otra referencia ya que solo hay un objeto.
SetLength(a, 1);
a[0] := 42;
b := a;
b[0] := 666;//now a[0]=666
Usted pregunta si esto es correcto:
Move(p^, FData, n);
No, no es. Lo que ha hecho aquí es copiar el contenido de p
en el FData
referencia. Si quiere copiar con Move
, puede escribir:
Move(p^, Pointer(FData)^, n);
O si prefiere ser un poco más prolijo y evitar el reparto, puede escribir:
if n>0 then
Move(p^, FData[0], n);
Personalmente, no me siento mal por el elenco ya que Move
tiene ningún tipo de seguridad de todos modos.
¿Sería legal reutilizar FData ahora? ¿Quizás copiando datos nuevos (diferentes) en él?
No creo que pueda responder a esto sin más contexto. Por ejemplo, no sé por qué FData
es un campo, ya que solo se usa localmente para esa función. Tendría más sentido como una variable local. Presumiblemente hay una razón por la que se declara como un campo, pero no se puede distinguir fácilmente de este código.
Usted acerca de usar el patrón productor / consumidor. Normalmente esto se hace para separar la producción del consumo. Sin embargo, su código de ejemplo no hace esto, presumiblemente porque el código desacoplado sería demasiado complejo como para incluirlo aquí.
Para una verdadera implementación del productor / consumidor, debe transferir la propiedad de los datos del productor al consumidor. Según lo que hemos descrito anteriormente, una manera muy simple y efectiva de hacer esto es usar el recuento de referencias. Cuando los datos se transfieren al consumidor, el productor debe divulgar su referencia al mismo.
Mover (p ^, FData, n); Esto esta bien
procedimiento TClassB.ProcessData (d: TBytes); // d es el recuento de referencia de FData begin // d no contiene nada más que FData se queda como antes con refcount = 1 // si pone la palabra clave "var" delante de d, se lanzará FData SetLength (d, 0);
fin;