ゆとりーなの日記

日記的な事を書いて行くと思はれる

ポインタブル

昨日の記事、予約語特集ですがタイトルが爆死だとぱっと見難の記事か分からないですね。この日記のタイトルは記事内容というよりは今日あったことを呟く欄になっている感があるのであまりあてになりません。カテゴリで見ると間違いが減ります。まあ今日は少し記事の内容とタイトルを合わせてみました。あんまり合ってないかもしれませんが雰囲気です。

スマートポインタ特集

特集第2弾です。最近と「とある電気系出身者のいんでっくす」というブログに触発されてスマートポインタ関連の議論が熱いので個人的な見解を。

スマートポインタ(以下スマポ)ってなんぞ

破棄されるタイミングに空気を呼んでdeleteを自動でやってくれる賢いポインタです。自身のデストラクタが呼ばれた時にときに空気を読むタイプや、コピーされたものも含めて全てデストラクタが呼ばれた時に空気を読むタイプなど様々です。前者がauto_ptr、unique_ptr、scoped_系、後者がshared_系です。サポート系としてweak_ptrがあります。

スマポにはどんなのがあるの?

割と有名なスマポ達の一覧です。

名前 参照カウンタ コピー ライブラリ その他
auto_ptr なし std コピーするとコピー元はNULLになる
shared_ptr あり std::tr1、boost 循環参照に弱い
デリータを指定できる
遅め
サイズ大きめ
weak_ptr なし std::tr1、boost 循環参照対策
shared_ptrとセットで使い
対応するshared_ptrが落ちるとweak_ptrもNULLを指す
scoped_ptr なし 不可 boost デリータが指定できない
unique_ptr なし 不可 std(0x) scoped_ptrと同じ?
shared_array あり boost 配列用shared_ptr
scoped_array なし 不可 boost 配列用scoped_ptr
intrusive_ptr なし boost 参照カウンタ機構を備えるポインタに使う
例:COM等

他にもMSDNなんかに色々なスマートポインタが載ってます。

ファクトリ関数の返り値の話

で、スマポがらみの何の議論が熱いのかというと、返すのは生ポインタかスマポかという議論です。安全第一ならスマポ一択なのですが常にshared_ptr返しというのはなんか抵抗があるんですよね。scoped_ptrで事足りるときも、わざわざ遅いshared_ptr使わなきゃいけなくなるわけで。生ポインタを返すファクトリ関数をスマポに代入させる方が自由度が高くていいのではと思ってしまうわけです。生ポインタそのまま使ってリソースリークするのは残当ということにして。この発想はwin32のミューテクスなりファイルなりのHANDLEを全てデリータ指定のスマポにぶち込んできた人の発想なのかもしれませんが。

COMはめんどくさい

唯ね、スマートポインタだったら楽なのになと思う例もあるわけですよ。Direct3Dですね。Direct3Dに限らずCOMは特殊な形態しているので(ReleaseとかAddRefをこちらで管理する等)分かりにくいんですよね。なんでもないメソッドに生ポインタぶち込むといつの間にか参照カウンタインクリメントしてたり、MSDNを見てびっくりすることもあります。これが全部スマポであればこんなこと気にせずに済むんです。あとCOMのファクトリ関数はスマポと相性悪いです。COMのファクトリ関数は概ね次のようなインターフェイスになります。

HRESULT CreateCOM(色々, ICom **p);

ダブルポインタを取ってくるんですよね。返り値でポインタを返してくれないんです。例外使わずにエラーコードを使うことの害悪性が出ています。これがどう不便かというと、返り値がポインタならば、

std::tr1::shared_ptr<ICom> p(CreateCom(色々), デリータ));

で済むのに、ダブルポインタを取るせいで、

ICom *temp;
CreateCom(色々, &temp);
std::tr1::shared_ptr<ICom> p(temp, デリータ);

としなければいけなくなります。不便なことこの上ないです。
これに対応しようとこんなCOM用のスマートポインタを書く人もいると思います。

template <typename T>
class com_ptr {
public:
  T** operator &() {
    return &p_;
  }
private:
  T *p_;
};

これはまずい設計です。operator&を使ってp_の値を変えられると参照カウンタ系が狂ってきます。基本的にprivateメンバのポインタを返すべきではありません。どうしてもやりたいなら「マルペケつくろーどっとコム」で挙げられている様に、

T** com_ptr::toCreator(){
  if(p_) {
    p_->Release();
  }
  return &p_;
}

として、生成メソッドに渡す用のインターフェイスを用意する必要があります。
今回も無事おしまいです。