2012-07-16 31 views
17

¿Alguien puede dar una explicación de una referencia débil en Delphi?"Referencia débil": explicación realista

Me di cuenta de que el concepto a menudo se menciona en algún código fuente de biblioteca/marco que examino. Estoy en un limbo y quiero tener una comprensión clara de eso.

Respuesta

34

Las instancias que se referencian entre sí mediante referencias de interfaz se mantienen vivas entre sí en una implementación de interfaz basada en recuento de referencias.

Una referencia débil se utiliza para romper el abrazo del oso "manténganse vivos". Esto se hace declarando una referencia como un puntero puro para eludir el mecanismo de conteo de referencia.

IFriend = Interface(IInterface) 
end; 

TFriend = class(TInterfacedObject, IFriend) 
private 
    FFriend: IFriend; 
end; 


var 
    Peter: IFriend; 
    John: IFriend; 
begin 
    Peter := TFriend.Create; 
    John := TFriend.Create; 

    Peter.Friend := John; 
    John.Friend := Peter; 
end; 

Incluso cuando Pedro y Juan van fuera de alcance, sus casos se mantienen alrededor porque su referencia mutua mantiene su refcount caiga a cero.

El problema se encuentra más comúnmente en los patrones de composición (relaciones padre - hijo) donde el niño tiene una referencia de nuevo a los padres:

ISomething = Interface(IInterface) 
end; 

TSomething = class(TInterfacedObject, ISomething) 
end; 

TParent = class(TSomething) 
    FChildren: TInterfacedList; 
end; 

TChild = class(TSomething) 
    FParent: ISomething; 
end; 

Una vez más, los padres y el niño puede mantener entre si alrededor porque su referencia mutua evita que su refcount caiga a cero.

Esto se soluciona con un weak reference:

TChild = class(TSomething) 
    FParent: Pointer; 
end; 

Al declarar la FParent como un "puro" puntero del mecanismo de conteo de referencia no entran en juego para la referencia de nuevo a la matriz. Cuando un padre sale del alcance, su recuento de referencia ahora puede caer a cero porque sus hijos ya no mantienen su recuento de ref encima de cero.

Nota Esta solución requiere una atención especial para la administración de por vida. Los niños pueden mantenerse vivos más allá del tiempo de vida del padre cuando algo en el "exterior" de estas clases mantiene una referencia a un niño. Y esto puede conducir a todo tipo de AV's interesantes cuando el niño asume que la referencia principal siempre apunta a una instancia válida. Si lo necesita, asegúrese de que cuando el padre se salga del alcance, haga que los niños no tengan referencias anteriores antes de que anule sus propias referencias a sus hijos.

+0

1 para la explicación y la foto de perfil –

+0

@JanDoggen: :-) animales siempre ha sido mi favorito del muppet –

10

Por defecto en Delphi, todas las referencias son o bien:

  • weak references para pointer y class casos;
  • copia explícita de bajo nivel de los tipos de valor como integer, Int64, currency, double o record (y viejos en desuso o objectshortstring);
  • copy-on-write con reference counting para alto nivel tipos de valor (por ejemplostring, widestring, variant o matriz dinámica);
  • referencia fuerte con recuento de referencias para interface instancias;

El principal problema con fuerte recuento de referencias es el potencial circular problema referencia . Esto ocurre cuando un interface tiene una referencia fuerte a otro, pero el objetivo interface tiene un puntero fuerte al original. Incluso cuando se eliminan todas las otras referencias, todavía se conservarán entre sí y no se liberarán. Esto también puede ocurrir indirectamente, mediante una cadena de objetos que podría tener el último en la cadena que hace referencia a un objeto anterior.

Ver la siguiente definición de la interfaz, por ejemplo:

IParent = interface 
    procedure SetChild(const Value: IChild); 
    function GetChild: IChild; 
    function HasChild: boolean; 
    property Child: IChild read GetChild write SetChild; 
    end; 

    IChild = interface 
    procedure SetParent(const Value: IParent); 
    function GetParent: IParent; 
    property Parent: IParent read GetParent write SetParent; 
    end; 

La siguiente aplicación será definitivamente perder memoria:

procedure TParent.SetChild(const Value: IChild); 
begin 
    FChild := Value; 
end; 

procedure TChild.SetParent(const Value: IParent); 
begin 
    FParent := Value; 
end; 

En Delphi, tipo más común de variables de referencia en papel (es decir, la variante, dinámico array o cadena) resuelva este problema implementando copy-on-write. Desafortunadamente, este patrón no es aplicable a la interfaz, que no son objetos de valor, sino objetos de referencia, vinculados a una clase de implementación, que no se pueden copiar.

Tenga en cuenta que basó el lenguaje (como Java o C#) no sufren de este problema, ya que las referencias circulares son manejadas por su modelo de memoria: los objetos de por vida son mantenidos globalmente por el administrador de memoria. Por supuesto, aumentará el uso de memoria, ralentizará el proceso debido a acciones adicionales durante la asignación y las asignaciones (todos los objetos y sus referencias deben mantenerse en listas internas) y puede ralentizar la aplicación cuando el recolector de elementos no utilizados entra en acción.

Una solución común con los idiomas sin recogida de basura (como Delphi) es utilizar Punteros débiles, mediante los cuales se asigna la interfaz a una propiedad sin incrementar el recuento de referencias. Con el fin de crear fácilmente un puntero débil, la siguiente función se podría utilizar:

procedure SetWeak(aInterfaceField: PIInterface; const aValue: IInterface); 
begin 
    PPointer(aInterfaceField)^ := Pointer(aValue); 
end; 

Por lo tanto, podría ser utilizado como tal:

procedure TParent.SetChild(const Value: IChild); 
begin 
    SetWeak(@FChild,Value); 
end; 

procedure TChild.SetParent(const Value: IParent); 
begin 
    SetWeak(@FParent,Value); 
end; 

se puede tratar de leer my blog post about weak references in Delphi - y su fuente asociada código: hemos implementado la referencia de debilidad directa y el manejo de la interfaz de referencia débil "cero" desde Delphi 6 hasta XE2.

De hecho, en algunos casos, deberá configurar los campos débiles de la interfaz en nil, si libera la instancia de referencia antes de su hijo, para evitar cualquier problema de Infracción de acceso. Esto se llama "Poniendo a cero los punteros débiles", y lo que Apple implemented with the ARC model, y que intentamos implementar en Delphi.

+0

C# también tiene un [WeakReference] (http://msdn.microsoft.com/de-de /library/system.weakreference.aspx) clase para permitir que el GC limpie ese objeto una vez que no haya otras referencias. –

+0

@StefanGlienke Tienes razón, pero en un mundo de GC, las referencias débiles son algo diversas, ya que son un tipo particular de clase que se usa para pasar por alto la propia recolección de basura. En Delphi, no tenemos ninguna derivación de GC, solo una referencia a un objeto con su propia memoria administrada. Los puntos débiles (cero) en las interfaces Delphi son algo más bajos que este. –

+0

De acuerdo, solo quería señalar que esto no es solo un problema Delphi, aunque el GC es mucho más inteligente en el manejo de referencias cruzadas/circulares. –

2

En el caso más general, un strong reference controla la vida útil de una instancia referenciada mientras que un weak reference no lo hace. El término weak reference se puede usar en contexto de un recolector de basura, interfaces de referencia contada u objetos comunes.

Por ejemplo, un formulario Delphi contiene las referencias a todos sus controles; estas referencias se pueden llamar fuertes porque cuando se destruye una forma, sus controles también se destruyen. Por otro lado, el control de un formulario Delphi tiene una referencia a una forma a la que pertenece. Esta referencia puede llamarse débil porque no controla la duración de una forma de ninguna manera.

Cuestiones relacionadas