インターフェース参照 vs ジェネリックインターフェース制約

インターフェース参照を使用すると参照カウンターが使用されるので、意図しないタイミングでオブジェクトが破棄されてしまうことがあります。

たとえば、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.

コメント

  1. 某所 ( 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 にバグとしてレポートするのが正しい道だと思います。すでに登録済みならレポート番号を載せていただくと他の方の手間が省けます。

  2. バグではありませんよ。

    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. 」

  3. 続報

    —-
    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 の言及も、しかしジェネリクス版とそうでないもので動作が違う。という、説明しがたい不自然点を露わにしているだけに見えますね。

  4. 知らなかった人が少なくないようですね。
    記事を書いた甲斐がありました。
    インターフェース参照とジェネリックインターフェース制約の違いを理解して、うまく使い分けたいですね。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です