Swanman's Horizon

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

スマートポインタ、使ってる?

使ってません(でした)

僕自身も2009年頃に匿名メソッド使ってスマートポインタ作れるよ的な記事を書いてましたが、ぶっちゃけ使ってませんでした。
というのも、こちらの実装を見てもらうと分かるんですが、書くのが面倒だったんですね。
後述のクラス等と合わせるためにリンク先とはクラス名は違いますが、

procedure Test1;
var
  sl: TFunc<TStringList>;
begin
  sl := TAuto<TStringList>.Create(TStringList.Create); // TStringListを2回書かないといけない!
  sl.Add('hogehoge');
end;

こんな感じでやたら長くなるので、それだったら別にtry-finally書いちゃうかなと思ってました。

使い始めました(実装1)

で、ある時「そういえばDelphiの(超限定的な)型推論使うと短くなるかも」と思い実装したところ、結構短くなりました。短くなったおかげで気軽に使えるようになったので、ちょっとしたコードで使い始めます。
実装はひとまず置いといて、実際に使う時はこんな感じ。

procedure Test2;
var
  sl: Auto<TStringList>;
begin
  sl := Auto.Wrap(TStringList.Create); // TStringListを1回書くだけでOK
  sl.Add('hogehoge');
end;

ただし問題が

上二つの方法に共通するのが、slはTStringListのインスタンス変数そのものではなく、TStringListのインスタンスを返すメソッドということです。
擬似的なコードを書くと、こんなものが存在していてそれを毎回コールしているわけです。つまり変数を使う回数が多ければ多いほど、オーバーヘッドが発生してしまいます。

function sl: TStringList;
begin
  Result := Instance;
end;

じゃあどうするか(実装2)

変数を使う度に関数呼び出しが発生するのは(速度的にシビアな場面じゃなければ)問題ないんですが、気になることは気になるので出来れば素のインスタンスを扱いたいわけです。おまけに勝手に解放されて欲しい!
ということで最近はこっちの実装を使うようになりました。

procedure Test3;
var
  sl: TStringList; // 普通の変数宣言と一緒!
begin
  sl := Auto.Wrap(TStringList.Create); // ここは実装1と同じ
  sl.Add('hogehoge');
end;

ここまで来ると普通にtry-finallyを使うより早く書けるので、ちょっとした処理には結構便利です。

解説1

まず実装1の方から。コードはこちら。http://twc.xrea.jp/code/SmartPointer1.txt
こちらは最初に貼ったリンク先のBarry Kelly氏の実装に、型推論で型を省略するために新しくAutoクラスを作り、その中にクラスメソッドとして実際の処理を書くという、わりと単純なものです。

解説2

次に実装2。コードはこちら。http://twc.xrea.jp/code/SmartPointer2.txt
使う時の書き方こそ実装1と同じ「Auto.Wrap(...)」ですが、Wrapの戻り値が全く異なります。リンク先の実装を見てもらえると分かりますが、実はrecord(TAutoRelease)を返してます。
さらに、このrecordは演算子オーバーロードを使って暗黙的にTAutoReleaseからTに変換するImplicitを実装しています。これがあるのでAuto.Wrapは引数に取ったクラスと同じ変数に代入できるようになっています。
この表には出てこないけど裏ではこっそり生成されているrecordが重要で、このrecordが存在することにより、recordのフィールドとして存在するインターフェースを解放する処理が変数スコープの最後(上の例だとTest3のendあたり)に埋め込まれ、そのおかげでインスタンスを解放することが可能になってます。

実装1の利点と実装2の欠点

実装1は単なるインターフェースなので、例えばクラスのフィールドで使ったり、他のクラス等に渡してそっちで管理してもらったり、ということが普通に出来ます。さらに使用中の変数に新しく代入すると、その時点で以前に生成したインスタンスは解放されるので、nilを代入すれば任意のタイミングで解放ができます。
一方、実装2は任意の手続き内だけとか、かなり狭い範囲でしか使えません。インスタンスを生成した時のスコープで解放されてしまうからです。解放処理もスコープの最後に固定されているので、任意のタイミングで解放することはできません。特定のメソッド内だけで完結するような、本当にちょっとした処理用。
こういう事情もあり、実際は両方とも使う機会があって、僕自身もこういう実装を使ってます。万能って難しい…。

本日のまとめ

次世代コンパイラでWin32/64にもARCが来たら不要になるね(にっこり