Swanman's Horizon

性欲をもてあましつつなんらかの話をするよ。

Delphiでスマートポインタを。

ティファニーで朝食を、みたいな感じで書いたつもりが全然元ネタの香りもしない感じになったのは置いとくとして、Delphiジェネリクスが実装されたのでスマートポインタも実装し放題な今日この頃、というか時期的にはもう旬は終わった感もあるくらいなので、各地にはたくさんの実装がニョキニョキとすでに生まれてたりとか何とか。で、その中で一番シンプルで素敵だった実装(+Lynaたんによる改悪)がこちら。

unit Lyna.Generics;

interface

uses
  SysUtils;

type
  TSmartPointer<T: class, constructor> = class(TInterfacedObject, TFunc<T>)
  private
    FValue: T;
  public
    constructor Create(AValue: T); overload;
    constructor Create; overload;
    destructor Destroy; override;
    function Invoke: T;
    property Value: T read FValue;
  end;

implementation

{ TSmartPointer<T> }

constructor TSmartPointer<T>.Create(AValue: T);
begin
  inherited Create;
  FValue := AValue;
end;

constructor TSmartPointer<T>.Create;
begin
  inherited;
  FValue := nil;
end;

destructor TSmartPointer<T>.Destroy;
begin
  FValue.Free;
  inherited;
end;

function TSmartPointer<T>.Invoke: T;
begin
  if not Assigned(FValue) then
    FValue := T.Create;
  Result := FValue;
end;

end.

例えばIDE上でTFuncなどにカーソルを合わせてもらうと分かるんだけど、「type SysUtils.TFunc`1: interface(IInterface)」となるように、無名メソッド型というのはDelphiのインターフェース型なんだよね。だからインターフェースとして扱えちゃうし、もちろんクラス宣言にも書けちゃう。仕組みが分かれば何てことはないけど、最初は「その手があったかー!」みたいな。

ちなみに拡張したところは、型パラメータにconstructor制約を課すことによって遅延生成対応にした部分。正直イラネと思われるかもしれないけど、ただでさえ以下のように記述量が多くなりがちなので、

var
  http: TFunc<TIdHTTP>;
  s: string;
begin
  http := TSmartPointer<TIdHTTP>.Create(TIdHTTP.Create); // 'TIdHTTP''Create'を二度も書く必要があるなんて><
  s := http.Get('http://twc.xrea.jp');
  Memo1.Lines.Add(s);
end;

次のようにすることでちょっとだけ記述を減らせる。まぁそれだけなんだけど。

var
  http: TFunc<TIdHTTP>;
  s: string;
begin
  http := TSmartPointer<TIdHTTP>.Create(); // Createの後の()は省けないけど、上のよりはすっきり
  s := http.Get('http://twc.xrea.jp'); // ここで初めてTIdHTTPのインスタンスが生成される
  Memo1.Lines.Add(s);
end;

ただしconstructor制約下では引数を取らないコンストラクタしか呼び出せないので、THoge.Create('hogehoge')と書いて生成しなければならないようなクラスは上の例のような書き方のみ使える*1

*1:下の例みたいに書いても通っちゃうけど、継承元を辿って引数のないコンストラクタが呼び出されるだけ