いろいろバックエンドな人の備忘録

といいつつレイヤーのネタ書いてます。C/C++, Ruby(Rails)、自宅サーバー,PCパーツ,ネットワーク、あとピアノも。

<メモブログ>[未解決]shared_ptr"のポインタ"はどう扱えばいいのだ・・・

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つ思いつく。

  1. shared_ptr のポインタ を格納する。
  2. 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
} // ここで sptr 自体は無効になる。C言語をやっていれば当たり前のことではあるが。。。

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 ); //  この shared_ptr の参照カウントはまた 0 になる

  c_plus_plus_function( hoge_sptr  ); //c_plus_plus_function(const shared_ptr<T>& _hoge  );
} 

解決(?)

静的クラスでも、静的変数でもいいので、std::unorderd_mapstd::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等でもおこるはず。いい方法があったら教えてください。