インターフェース参照を使用すると参照カウンターが使用されるので、意図しないタイミングでオブジェクトが破棄されてしまうことがあります。
たとえば、TPersonクラスはIPersonインターフェースを継承しています。
type
IPerson = interface
procedure Say;
end;
TPerson = class(TInterfacedObject, IPerson)
private
FName: string;
public
constructor Create(const AName: string);
destructor Destroy; override;
procedure Say;
end;
{ TPerson }
constructor TPerson.Create(const AName: string);
begin
FName := AName;
end;
destructor TPerson.Destroy;
begin
WriteLn('Destroy');
inherited;
end;
procedure TPerson.Say;
begin
WriteLn('Hello ' + FName);
end;
TTestクラスのSay関数はインターフェースを引数に取ります。
type
TTest = class
class procedure Say(Arg: IPerson); overload; static;
end;
class procedure TTest.Say(Arg: IPerson);
begin
Arg.Say;
end;
次のようにTPersonクラスのオブジェクトを、インターフェースを引数に取る関数に渡すと、関数が終わったときにオブジェクトが破棄されてしまいます。
var
Person: TPerson;
begin
Person := TPerson.Create('Alice');
TTest.Say(Person); // ここでPersonオブジェクトが破棄される
Person.Say;
end.
ジェネリックインターフェース制約は参照カウンターを使用しません。
そのため、上のような問題は発生しません。
type
TTest = class
class procedure Say<T: IPerson>(Arg: T); overload; static;
end;
class procedure TTest.Say<T>(Arg: T);
begin
Arg.Say;
end;
このようにジェネリックインターフェース制約を使用します。
そうすると、参照カウンターは使用されず、オブジェクトは破棄されません。
var
Person: TPerson;
begin
Person := TPerson.Create('Alice');
TTest.Say(Person); // ここでPersonオブジェクトが破棄されない
Person.Say;
end.
某所 ( echo.2ch.net/test/read.cgi/tech/1458356584/348-n ) でこんなこと書かれていますが
—-
348 : デフォルトの名無しさん2016/07/12(火) 20:59:00.94 ID:L8/u54R7
堂々と紹介してるけどこの挙動が本当ならバグじゃないの?
349 : デフォルトの名無しさん2016/07/12(火) 21:23:03.27 ID:ub257ayT
そもそもまともな仕様書らしきもの公開してない時点で、バグだの違うだのとか
議論しても無駄だしどうでもいいかな。
350 : デフォルトの名無しさん2016/07/12(火) 22:39:47.89 ID:O6snlBaz
>>348
彼が勘違いしてるだけだから気にすんな
—-
IDera にバグとしてレポートするのが正しい道だと思います。すでに登録済みならレポート番号を載せていただくと他の方の手間が省けます。
バグではありませんよ。
Object Pascal Handbook(http://cc.embarcadero.com/item/30018)には、次のように書かれています。
「In my opinion, the key difference is that using the interface-based version means having Object Pascal’s reference counting mechanism in action, while using the generic version the class is dealing with plain objects of a given type and reference counting is not involved. 」
続報
—-
358 : デフォルトの名無しさん2016/07/14(木) 19:21:32.94 ID:KR339VIQ
>>353
参照カウンタの推移
1. インスタンス化してTTest型の変数に代入 -> +0
2. ITest型の引数に渡して、メソッド実行 -> +1
3. メソッドからのリターン -> -1
3.1. ここで参照カウンタが0になるのでオブジェクトがランタイムに破棄される
というコードに対して、メソッドから抜け出たら、オブジェクト死んでる!おかしい!って叫んでるだけ
359 : デフォルトの名無しさん2016/07/14(木) 19:23:39.12 ID:KR339VIQ
>>353
じゃあ、どうすべきだったかといえば、初めからITest型の変数に代入しておけばいいだけ。
ジェネリック制約どうこう以前のおはなし。
—-
Object Pascal Handbook の言及も、しかしジェネリクス版とそうでないもので動作が違う。という、説明しがたい不自然点を露わにしているだけに見えますね。
知らなかった人が少なくないようですね。
記事を書いた甲斐がありました。
インターフェース参照とジェネリックインターフェース制約の違いを理解して、うまく使い分けたいですね。