TL;DR
- C++ と mruby
- shared_ptr"のポインタ"を void* にキャストして保存するときは、vector等のコンテナ要素をポインタにする
- というか、ベストプラクティスがわからん、そもそもこの設計はよくないかも。
問題
C++ と mruby を使ったプログラムを書いている。
C++の世界では、ほぼ全てのオブジェクトが、shared_ptr<T>
で管理されており、例えばライブラリのインターフェイスの引数も const shared_ptr<T>&
になっている。
C++の世界だけで完結するならこれでいいのだけれども、mrubyをバインディングしようとすると困った。
このような、グルー言語系のオブジェクトは void*
で持たせるようになっていて、mrubyだとDATA_PTR
マクロでRubyのオブジェクト型であるmrb_value
型を渡すと、void*
のポインタが返ってくる。
ここにオリジナルの構造体や値のポインタを入れれば良い。
さて、ここで、 C++ のshared_ptr<T>
のオブジェクトをRuby側にもたせようとする方法は2つ思いつく。
- shared_ptr のポインタ を格納する。
shared_ptr<T>#get()
で生ポに変換して格納する。
なお、いずれの場合もshared_ptr<T>
の参照カウントは無効になる。(これは void* に格納する以上諦めるしかない。)
1.の場合はハマる確率がかなり高い。
色々原因はあるが、まず、 shared_ptr自体のオブジェクトを関数内で生成するタイプならば、その関数を抜けた時点で、格納したオブジェクトは無効になる。
なので、次に使おうとしたときに、アクセス違反になる。(たまに動くけど、それは偶然でコード的にはNG)
void create_object() {
shared_ptr<HOGE> sptr = std::make_shared<HOGE>();
DATA_PTR(rb_object) = &sptr
}
2.場合は格納は問題ないが、次にC++の世界に戻ってきたときに困る。C++のインターフェイスにオブジェクトを渡すときに型が一致しないために、shared_ptr オブジェクトを作ってやる必要がある。
そのときに、参照カウントがすでにある shared_ptr<T>
と矛盾してしまう。
void called_by_ruby() {
HOGE *hoge_ptr = static_cast<HOGE*>( DATA_PTR(rb_object) );
const shared_ptr hoge_sptr = shared_ptr<HOGE>( hoge_ptr );
c_plus_plus_function( hoge_sptr );
}
解決(?)
静的クラスでも、静的変数でもいいので、std::unorderd_map
か std::vector
等を作り、そのインデックスを指してやる。
今回は、std::unorderd_map
にし、キーを mrb_value
にした。当然ハッシュ関数も必要なので作ってやる。(DATA_PTRのポインタをキーにする)
struct Mruby_data_value_hash
{
size_t operator()(const mrb_value& m_val) const { return reinterpret_cast<size_t>(DATA_PTR(m_val)); }
};
struct Mruby_data_value_equal
{
bool operator()(const mrb_value& left, const mrb_value& right) const { return DATA_PTR(left) == DATA_PTR(right); }
};
std::unorderd_map<mrb_value, std::shared_ptr<HOGE>, Mruby_data_value_hash, Mruby_data_value_equal> sptr;
mrb_value mrb_hoge_init(mrb_state* mrb, mrb_value self) {
DATA_PTR(self) = (&sptr[self]);
}
なお、そもそも設計がマズいかもしれない。理屈は Lua等でもおこるはず。いい方法があったら教えてください。