誰かが Delphiの弱い参照について説明できますか?
私が精査しているいくつかのライブラリ/フレームワークのソースコードで、この概念がしばしば言及されていることに気付きました。私は途方に暮れていて、それを明確に理解したいと思っています。
誰かが Delphiの弱い参照について説明できますか?
私が精査しているいくつかのライブラリ/フレームワークのソースコードで、この概念がしばしば言及されていることに気付きました。私は途方に暮れていて、それを明確に理解したいと思っています。
インターフェイス参照によって相互に参照するインスタンスは、参照カウント ベースのインターフェイス実装で相互に存続します。
弱い参照は、「お互いを生かしておく」ベア ハグを破るために使用されます。これは、参照カウントメカニズムを回避するために、1 つの参照を純粋なポインターとして宣言することによって行われます。
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;
Peter と John がスコープ外に出た場合でも、相互参照によって refcount がゼロにならないため、インスタンスは保持されます。
この問題は、子が親への後方参照を持つ複合パターン (親子関係) でより一般的に見られます。
ISomething = Interface(IInterface)
end;
TSomething = class(TInterfacedObject, ISomething)
end;
TParent = class(TSomething)
FChildren: TInterfacedList;
end;
TChild = class(TSomething)
FParent: ISomething;
end;
ここでも、相互参照によって参照カウントがゼロにならないようにするため、親と子はお互いを維持できます。
これは次のように解決されますweak reference
:
TChild = class(TSomething)
FParent: Pointer;
end;
FParent を「純粋な」ポインタとして宣言することにより、参照カウント メカニズムは、親への後方参照に対して機能しなくなります。親がスコープ外に出ると、その参照カウントが 0 になる可能性があります。これは、その子が 0 を超える参照カウントを維持できなくなるためです。
注:このソリューションでは、有効期間の管理に細心の注意を払う必要があります。これらのクラスの「外側」にある何かが子への参照を保持している場合、子は親の寿命を超えて生き続けることができます。そして、これは、子が親参照が常に有効なインスタンスを指していると想定すると、あらゆる種類の興味深い AV につながる可能性があります。必要な場合は、親がスコープ外になったときに、子への参照を nil する前に、子の後方参照を nil にするようにしてください。
Delphi のデフォルトでは、すべての参照は次のいずれかです。
pointer
とclass
インスタンスの弱参照。integer, Int64, currency, double
record
object
shortstring
string, widestring, variant
interface
インスタンスの参照カウントによる強参照。強力な参照カウントの主な問題は、潜在的な循環参照の問題です。これはinterface
、 が別のものへの強い参照を持っているが、ターゲットinterface
がオリジナルへの強いポインタを持っている場合に発生します。他のすべての参照が削除されても、それらは互いに保持され、解放されません。これは、チェーンの最後のオブジェクトが以前のオブジェクトを参照している可能性があるオブジェクトのチェーンによって、間接的に発生することもあります。
たとえば、次のインターフェイス定義を参照してください。
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;
次の実装では、確実にメモリ リークが発生します。
procedure TParent.SetChild(const Value: IChild);
begin
FChild := Value;
end;
procedure TChild.SetParent(const Value: IParent);
begin
FParent := Value;
end;
Delphi では、最も一般的な種類の参照コピー変数(つまり、バリアント、動的配列、または文字列)は、コピー オン ライトを実装することでこの問題を解決します。残念ながら、このパターンは、値オブジェクトではなく、コピーできない実装クラスに関連付けられた参照オブジェクトであるインターフェイスには適用できません。
ガベージ コレクター ベースの言語(Java や C# など) では、循環参照がメモリ モデルによって処理されるため、この問題が発生しないことに注意してください。オブジェクトの有効期間は、メモリ マネージャーによってグローバルに維持されます。もちろん、メモリ使用量が増加し、割り当てと割り当て中の追加のアクションによりプロセスが遅くなり (すべてのオブジェクトとその参照は内部リストで維持する必要があります)、ガベージ コレクターが動作に入るとアプリケーションの速度が低下する可能性があります。
ガベージ コレクションを持たない言語 (Delphi など) での一般的な解決策の 1 つは、参照カウントをインクリメントせずにインターフェースをプロパティに割り当てる、ウィーク ポインターを使用することです。弱いポインターを簡単に作成するには、次の関数を使用できます。
procedure SetWeak(aInterfaceField: PIInterface; const aValue: IInterface);
begin
PPointer(aInterfaceField)^ := Pointer(aValue);
end;
したがって、次のように使用できます。
procedure TParent.SetChild(const Value: IChild);
begin
SetWeak(@FChild,Value);
end;
procedure TChild.SetParent(const Value: IParent);
begin
SetWeak(@FParent,Value);
end;
Delphi の弱参照とそれに関連するソース コードに関する私のブログ投稿を読むことができます。Delphi 6 から XE2 まで、直接弱参照と「ゼロ化」弱参照インターフェイスの処理を実装しました。
実際、場合によっては、nil
アクセス違反の問題を回避するために、子の前に参照インスタンスを解放する場合、インターフェイスの弱いフィールドを に設定する必要があります。これは「Zeroing Weak pointers」と呼ばれ、Apple が ARC モデルで実装し、Delphi で実装しようとしたものです。
最も一般的なケースでstrong reference
は、a は参照されたインスタンスの存続期間を制御しますが、 a は制御weak reference
しません。この用語weak reference
は、ガベージ コレクター、参照カウント インターフェイス、または共通オブジェクトのコンテキストで使用できます。
たとえば、Delphi フォームはそのすべてのコントロールへの参照を保持します。フォームが破棄されるとそのコントロールも破棄されるため、これらの参照は強力であると言えます。一方、Delphi フォームのコントロールには、それが属するフォームへの参照があります。この参照は、フォームの有効期間をまったく制御しないため、weak と呼ぶことができます。