2011-09-21 26 views
16

¿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.Delphi XE TBytes uso correcto

Por ejemplo, en el objeto A, deseo ensamblar algunos datos en una matriz TBytes que es miembro del objeto A. Cuando eso esté completo, quiero pasar la matriz TBytes a otro objeto B, que luego se convierte en el dueño de los 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 de antemano!

Respuesta

13

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 en una de tres maneras:

a := nil; 
Finalize(a); 
SetLength(a, 0); 

Sin embargo, es muy común que simplemente no hacer nada y dejar que se liberará la referencia cuando la variable deja un amplio margen.

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 lo es. Lo que ha hecho aquí es copiar el contenido de p en la referencia FData. Si desea copiar con Move entonces se puede escribir:

Move(p^, Pointer(FData)^, n); 

O si usted prefiere ser un poco más detallado y evitar el reparto se puede escribir:

if n>0 then 
    Move(p^, FData[0], n); 

yo personalmente no se sienten demasiado mal sobre el elenco desde Move no tiene ningún tipo de seguridad de todos modos.


¿Sería legal para reutilizar ▶ DATA 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 utiliza el patrón de 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.

+0

Gracias por las explicaciones. Sí, la razón por la cual FData es una variable miembro se debe a que el productor obtiene datos de un socket TCP, por lo que generalmente solo obtiene una parte de los datos cada vez que se llama. Sí, sí quiero al productor/consumidor, y el comentario sobre "necesidad de transferir la propiedad de los datos" es exactamente lo que trato de hacer. Y sí, el código desacoplado era bastante complicado; de ahí la simplificación. Finalmente, todavía no estoy seguro de cómo "transferir la propiedad" correctamente. ¿Puedes aclarar un poco ese problema? –

+0

Deje que el consumidor tome una referencia y luego libere la referencia del productor. Consumer.data: = FData; FData: = nil; –

-2

Mover (p ^, FData, n); Esto está bien

procedure 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 palabra clave "var" delante de d, se lanzará FData SetLength (d, 0);
final;

+1

No, no está bien. Mueve los bytes de la dirección en p al puntero en FData. Debería ser FData [0]. –

4

Hay un par de usos incorrectos en su 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;