2009-04-23 17 views
7

En mi código utilizo una pequeña clase de almacenamiento de datos, que se crea en diferentes lugares. Para evitar pérdidas de memoria y simplificar las cosas, quiero utilizar el recuento de referencias, así que lo hiceRecuento de referencias para los objetos

type TFileInfo = class (TInterfacedObject, IInterface) 

y quitado todas mis llamadas manuales a TFileInfo.Free. Desafortunadamente Delphi reportó muchas pérdidas de memoria. Buscando en así que encontré la siguiente pregunta explicar por qué esto no funciona:

Why aren't descendants of TInterfacedObject garbage collected?

Hay una solución presentada allí, pero me obliga (al menos si lo consigo derecha) para escribir una interfaz personalizada IFileInfo y proporcionarle una gran cantidad de getters y setters, que quiero evitar.

EDITAR debo añadir que inserto el FileInfo crear objetos en dos tipos diferentes de tablas hash: uno que descienden de TBucketList y otro es una implementación mapa hash del foro Codegear. Internamente, ambos usuarios apuntan, por lo que la situación es como en la otra pregunta.

¿Hay alguna otra posibilidad de hacer que los objetos en Delphi usen recuento de referencias?

Respuesta

5

Por desgracia, el compilador Delphi genera el código necesario para inc cuenta de referencia/dec sólo cuando se utiliza las interfaces (en su caso interfaz personalizada IFileInfo). Además, si las interfaces se convierten en puntero (o TObject para el caso), de nuevo no es posible el recuento de referencias. Por ejemplo, assumming lista de variables globales: TList:

var ifi : IFileInfo; 
begin 
    ifi := TFileInfo.Create; 
    list.Add(TFileInfo(ifi)); 
end; 

después de la lista devuelve el método [list.Count - 1] contendrá colgando puntero.

Por lo tanto, las interfaces no se pueden utilizar en un hashmap que las arroje a punteros, la implementación de hashmap debe mantenerlas como IInterface.

+0

Pero, ¿el compilador genera las inc/dec correctas si inserto todos los objetos en un mapa hash? – jpfollenius

+0

respuesta editada con información adicional – Kcats

+0

Este código no se compila. No es posible encasillar interfaces a objetos. – dummzeuch

2

Esta funcionalidad se suministra para interfaces pero no para objetos.

Puede crear algo parecido, pero es necesario para anular algunas de la estructura de TObject:

TRefCountObject = class (TObject) 
private 
    FRefCount : Integer; 
public 
    constructor Create; 

    procedure Free; reintroduce; 

    function RefCountedCopy: TRefCountObject; 
end; 


constructor TRefCountObject.Create; 
begin 
    inherited; 
    FRefCount := 1; 
end; 

procedure TRefCountObject.Free; 
begin 
    if self=nil then Exit; 
    Dec(FRefCount); 
    if FRefCount<=0 then 
    Destroy; 
end; 

function TRefCountObject.RefCountedCopy: TRefCountObject; 
begin 
    Inc(FRefCount); 
    Result := self; 
end; 

Usted necesita RefCountedCopy para asignar el objeto a otra variable. Pero luego tienes un objeto refincado.

cómo utilizar este:

var1 := TRefCountObject.Create; // rc = 1 
var2 := var1.RefCountedCopy;  // rc = 2 
var3 := var1.RefCountedCopy;  // rc = 3 
var2.Free;      // rc = 2 
var1.Free;      // rc = 1 
var4 := var3.RefCountedCopy;  // rc = 2 
var3.Free;      // rc = 1 
var4.Free;      // rc = 0 
+0

Gracias por la respuesta detallada! Aunque no lo entiendo completamente. Todavía tengo que llamar a TRefCountObject.Free ¿verdad? ¿O cómo uso esto? – jpfollenius

+0

Agregó un poco de información de uso. –

+0

Así que todavía tengo que asegurarme de llamar gratis al menos una vez por cada objeto, ¿verdad? Y no hay forma de evitar esto? – jpfollenius

0

Hay una explicación larga para esto, pero en resumen: Heredar de TInterfacedObject (y no llamar a Free free), no es suficiente, necesita usar un objeto-fábrica-dinámica para crear los objetos para usted, y usar la interfaz - indica el objeto en todas partes, no solo las variables de referencia de objeto. (Sí, eso significa que no puede cambiar el 'código antiguo' sin examinarlo)

3

No mezcle referencias de objetos ni referencias de interfaz.

var 
    Intf: IInterface; 
    Obj: TFileInfo; 

begin 
    // Interface Reference 
    Intf := TFileInfo.Create; // Intf is freed by reference counting, 
          // because it's an interface reference 
    // Object Reference 
    Obj := TFileInfo.Create; 
    Obj.Free; // Free is necessary 

    // Dangerous: Mixing 
    Obj := TFileInfo.Create; 
    Intf := Obj; // Intf takes over ownership and destroys Obj when nil! 
    Intf := nil; // reference is destroyed here, and Obj now points to garbage 
    Obj.Free; // this will crash (AV) as Obj is not nil, but the underlying object 
      // is already destroyed 
end; 
8

El recuento de referencias en Delphi sólo funciona si sólo tiene una referencia a la instancia a través de una interfaz. Tan pronto como mezcle las referencias de la interfaz y las referencias de clase, entonces tendrá problemas.

Básicamente, desea contar la referencia sin la necesidad de crear una interfaz con todos los métodos y propiedades definidos en ella. Hay tres formas de hacerlo, y estas son aproximadamente del orden en que las recomendaría.

  1. Barry Kelly ha escrito una publicación sobre Smart Pointers. Utiliza los genéricos en Delphi 2009, pero estoy bastante seguro de que podrías codificarlo con las versiones específicas del tipo que estás utilizando si aún no usas el 2009 (es un gran lanzamiento por cierto).

  2. Otra forma que funciona con más versiones de Delphi y menos modificaciones es el value type wrapper de Janez Atmapuri Makovsek. Es un ejemplo implementado para TStringList, pero puedes adaptarlo para cualquier tipo.

  3. La tercera forma es crear un puntero interconectado (similar al puntero inteligente de Barry, pero no tan inteligente). Creo que hay uno en JCL, pero no recuerdo los detalles con certeza. Básicamente, esta es una interfaz que acepta una referencia TObject en la construcción. Luego, cuando el recuento de referencias llega a cero, llama gratis al objeto que le pasaste. Este método realmente solo funciona para instancias de corta vida que no pasa como parámetros porque separa la referencia contada de referencia de la referencia realmente utilizada. En su lugar, recomendaría uno de los otros dos métodos, pero si prefiere este método y quiere más información, hágamelo saber.

Así es la cosa acerca de Delphi, hay maneras libres de lograr cosas. La opción n. ° 1 es la mejor en mi opinión: obtenga Delphi 2009 y use ese método si puede.

¡Buena suerte!

+0

Leí su pregunta nuevamente y no sé si alguna de estas respuestas funcionará ya que sus listas aceptan objetos porque usan punteros. Estos métodos usan interfaces o registros. De nuevo, pasar a Delphi 2009 proporciona contenedores genéricos que funcionan con registros, interfaces, objetos o tipos nativos. –

+0

Si bien creo que tiene buenos puntos en su respuesta, ninguna de las tres formas ayuda con el problema que tiene el OP, ninguno de ellos puede usarse en lugar de una instancia de TObject, para eliminar la necesidad de llamar a Free(). – mghie

+0

@JimMcKeeth: estoy usando Delphi 2009, pero todavía carece de una implementación de mapa hash genérica y razonablemente eficiente, ¿no? – jpfollenius

1

Para agregar a lo que ya se ha dicho, si desea almacenar referencias a interfaces, en lugar de usar un TList, use un TInterfaceList. El conteo de ref trabajara consistentemente.

+0

El problema es: necesito una implementación eficiente del mapa hash. El que estoy usando (Barry Kelly lo publicó en el foro de código IIRC) utiliza punteros para que no pueda trabajar con interfaces a menos que tenga una implementación de mapas hash que almacene interfaces. – jpfollenius

3

Si desea eliminar las llamadas gratuitas en las instancias de TObject, es posible que desee consultar un recolector de basura para Delphi nativo. Conozco dos recolectores de basura diferentes y una técnica de recolección de basura, cada uno con pros y contras.

Probablemente uno de ellos funcione para ti.

+0

+1 ¡gracias! Los echaré un vistazo. – jpfollenius

Cuestiones relacionadas