type IFoo = interface procedure FooBar; end; TFoo = class(TInterfacedObject,IFoo) public procedure FooBar; end; TBar = class(TObject) public procedure Baz(const AFoo: IFoo); end; procedure TFoo.FooBar; begin // Do something. end; procedure TBar.Baz(const AFoo: IFoo); begin AFoo.FooBar; end;インタフェースとしてIFooと、その実装としてクラスTFooを用意し、クラスTBarにはconst修飾されたIFooを渡すBazというメソッドがあります。ここで
var Bar: TBar; begin Bar := TBar.Create; Bar.Baz(TFoo.Create); Bar.Free; end;とTBar.BazにTFoo.Createで生成したインスタンスを直接渡すと、TFooはIFooとしての参照カウントの制御を受けず、リークしてしまいます。一方で
var Bar: TBar; begin Bar := TBar.Create; Bar.Baz(TFoo.Create as IFoo); Bar.Free; end;とIFooにキャストしたものを渡すとリークしなくなります。
これは、インタフェース型の引数がconst修飾されているとメソッド内部で仮引数が変更されないことが保証されているため、仮引数にコピーしたことによる参照カウントの管理を行わない最適化が行われるのに対して、前者のコードでは呼び出し元で生成したインスタンスが(インタフェース型ではなく)クラス型としてしか管理されていないため、やはり参照カウントの管理を受けずに、リークしてしまう、ということのようです。しかし後者ではas IFooとキャストしたことでIFoo型の暗黙のローカル変数が用意され、これによって参照カウントによって正常に解放されます。
メソッド内部で仮引数を別のインタフェース型の変数やフィールドにコピーすると参照カウントの管理が行われて適切に解放が行われますが、これは実装に依存しますし、また引数をconst修飾していなければ大丈夫ですが、既存の実装の変更が必要になる、ということが問題になります。
このためconst修飾されたインタフェース型の引数としてその場で生成したインスタンスを渡す場合はインタフェース型にキャストするか、あるいは
type TFoo = class(TInterfacedObject,IFoo) public class function CreateAsIntf: IFoo; procedure FooBar; end; class function TFoo.CreateAsIntf: IFoo; begin Result := TFoo.Create; end; var Bar: TBar; begin Bar := TBar.Create; Bar.Baz(TFoo.CreateAsIntf); Bar.Free; end;このように生成したインスタンスをインタフェースとして返すようなメソッドを用意して、そちらを経由するか、ということになります。
元ねたはもちろんDalija PrasnikarさんのDelphi Event-based and Asynchronous Programmingの"13.4 In-place construction in a const parameter"。
0 件のコメント:
コメントを投稿