ゆとりーなの日記

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

int型をポインタっぽく扱うunique_ptrは意外とえげつないかも

 DXライブラリ等では画像ハンドルがintなので、これをstd::unique_ptrとカスタムデリータでどうにかして管理しようという試みが私の中でなされてきたわけです。
 std::unique_ptrはpointerとしてNullablePointerを取らなければいけないとなっているらしく、その点ではintをカスタムデリータにtypedef int pointer;と書くことでintをポインタとして扱うstd::unique_ptrは書けないことになるのですが、どうも現状この機能を実装しているコンパイラVC++2010及びg++4.6.0で試してみたところ、

#include <memory>

struct int_delete {
  typedef int pointer;
  void operator ()(const int handle) const {}
};

typedef std::unique_ptr<void, int_delete> int_handle;

int main() {
  int_handle a(0);
  int_handle b(1);
  int_handle c(2);
  int_handle d(nullptr);
  b.swap(d);
  return 0;
}

コンパイル通ってしまうのですね。N3290を見る限りだとデストラクタの部分に於いて、

もし get() == nullptr ならば何もしない。 さもなくば get_deleter()(get())するよ。

的な雰囲気な事が書いてあるので、これをそのまま実装するとnullptrと比較出来ないと駄目そうですが、どうも実装を見る限りMSVCもg++も

if (ptr_ != pointer()) {
  get_deleter()(get())
}

のような感じになっており、故にデフォルトコンストラクタが無効値になるってことと!=演算子で比較出来れば実は割となんとかなってしまうという雰囲気が漂っています。
 さて、とは言ってもDXライブラリの画像ハンドルの無効値は-1です。現状何故かintをpointerとして扱っても問題は起きないみたいですが、これは無効値が0の時に限ります。単純にintをpointerとして扱ってしまうと有効なハンドルである0の時に永遠に解放されない命をもったハンドルが誕生し、-1は無効値であるにもかかわらずdeleteされるという危機に瀕してしまいます。
 これを回避するには現状では次のようなラッパさんを書いてあげれば解決する雰囲気です。

#include <iostream>
#include <memory>

struct dxlib_graph_handle {
  dxlib_graph_handle() : handle_(-1) {}

  explicit dxlib_graph_handle(const int handle) : handle_(handle) {}

  bool operator !=(const dxlib_graph_handle &rhs) const {
    return !(handle_ == rhs.handle_);
  }

  int handle() const {
    return handle_;
  }

 private:
  int handle_;
};

struct dxlib_graph_delete {
  typedef dxlib_graph_handle pointer;
  void operator ()(const dxlib_graph_handle &handle) const {
    std::cout << handle.handle() << std::endl;
  }
};

typedef std::unique_ptr<void, dxlib_graph_delete> dxlib_graph;

int main() {
  dxlib_graph a(dxlib_graph_handle(1));
  dxlib_graph b(dxlib_graph_handle(0));
  dxlib_graph c(dxlib_graph_handle(-1));
  dxlib_graph d;
  dxlib_graph e(nullptr);
  a.swap(e);
  return 0;
}
1
0

 現状これで上手くいっているように見えます。デフォルトコンストラクタでは内部のハンドルとして無効値の-1を取るようにしているので、上手く-1以外のハンドルの時だけデリータが呼ばれるような動作になっています。
 ここで一つ問題になるのは explicit dxlib_graph_handle(int)のexplicit指定ですね。これが非explicitだと、

int main() {
  dxlib_graph a(1);
  dxlib_graph b(0);
  dxlib_graph c(-1);
  dxlib_graph d;
  dxlib_graph e(nullptr);
  int zero = 0;
  dxlib_graph f(zero);
  a.swap(e);
  return 0;
}

のように書くことが出来ます。この書き方を許した方が、画像ハンドル生成関数の戻り値を直接突っ込めるので幸せになれそうですが、これの実行結果を見ると愕然とするのでした。

0
1

 なんと、1の後にもう一回0が出力されてもよさそうなものですがそうはなりません。というのも数値リテラルの0を埋め込んだ方は、intからdxlib_graph_handleへの暗黙変換よりも先にstd::nullptr_tの方にマッチしてしまうのです。というわけで残念ながら定数としての0はstd::unique_ptr的には無効値として扱われてしまうのです。恐らく画像ハンドル生成関数の戻り値を直接突っ込む位の用途しかないとしても、0は無効値ではないのに無効値として扱われてしまう可能性が残されているのを見ると気持ち悪いので個人的にはexplicitを付けてintから暗黙変換効かないようにしたほうがいいのではないかとか思ってる次第です。
 というかNullablePointerとはなんだったのでしょうか、std::nullptr_tを取るコンストラクタがなくても何故か動いてしまうのはどうも落ち着きません。