ククログ

株式会社クリアコード > ククログ > 名古屋Ruby会議03:Apache ArrowのRubyバインディング(5) #nagoyark03

名古屋Ruby会議03:Apache ArrowのRubyバインディング(5) #nagoyark03

前回はGObject Introspectionでのエラーの扱いについて説明しました。今回は戻り値のオブジェクトの寿命について説明します。と、考えていたのですが、書いていたら、間違って「戻り値の」オブジェクトの寿命ではなく「一般的な」オブジェクトの寿命について説明してしまっていました。。。なので、今回は「一般的な」オブジェクトの寿命についての説明で、「戻り値の」オブジェクトの寿命は次回にします。。。実際に動くバインディングはkou/arrow-glibにあります。

Apache Arrowでのオブジェクトの寿命の管理:std::shared_ptr

Apache Arrowはstd::shared_ptrでオブジェクトの寿命を管理しています。バインディングは該当オブジェクトを使っているときは参照を増やして、使わなくなったら参照を減らせばよいです。

GObject Introspectionでのオブジェクトの寿命の管理:GObject

GObject Introspection対応ライブラリーはC言語で実装しますが、オブジェクト指向な機能を使えます。たとえば、カプセル化・継承・ポリモルフィズムなどの機能があります。

これらの機能はGObject IntrospectionがベースにしているGObjectというライブラリーが提供しています。GObject Introspectionでのオブジェクトの寿命の管理はこのGObjectの機能を使います。

GObjectではオブジェクトの寿命はリファレンスカウントで管理します。具体的にはGObjectを継承したクラスを作り、そのオブジェクトに対してg_object_ref()/g_object_unref()を使うことで寿命を管理します。

Apache ArrowのオブジェクトとGObjectのオブジェクトを適切な寿命でなじませるには次のようにします。

  • Apache ArrowのクラスとGObjectのクラスを1対1で対応させる。(例:arrow::ArrayGArrowArrayGObjectベースのクラス)に対応させる。)

  • GObjectのオブジェクトを作るときに対応するApache Arrowのオブジェクトの参照を増やす。

  • GObjectのオブジェクトを解放するときに対応するApache Arrowのオブジェクトの参照を減らす。

Apache ArrowのクラスとGObjectのクラスを1対1で対応させるのは簡単です。そうなるようにクラスを設計すればよいだけだからです。

GObjectのオブジェクトを作るときに対応するApache Arrowのオブジェクトの参照を増やすには、GObjectのオブジェクトのインスタンス変数としてstd::shard_ptr<ARROW_CLASS>なインスタンス変数を用意して、GObjectのコンストラクターで代入します。(GArrowArrayではプロパティーという仕組みを利用していますが、利用せずに直接構造体のメンバーを使ってもよいです。)

GObjectのオブジェクトを解放するときに対応するApache Arrowのオブジェクトの参照を減らすには、GObjectのオブジェクトが解放されるときに関数を呼ぶ仕組みがあるのでそれを利用します。(dispose()finalize()の2つ呼ばれる関数がありますが、この場合はfinalize()を利用します。dispose()は循環参照を解決するために利用します。)

arrow-glibでの実装

GArrowArrayを使って実際の実装を説明します。

まず、arrow::Array(Apache Arrowのオブジェクト)の参照を増やすためのインスタンス変数を用意します。このインスタンス変数は外部から参照できる必要はないのでプライベートな構造体に持たせます。(カプセル化)

typedef struct GArrowArrayPrivate_ {
  std::shared_ptr<arrow::Array> array;
} GArrowArrayPrivate;

G_DEFINE_TYPE_WITH_PRIVATE(GArrowArray, garrow_array, G_TYPE_OBJECT)

#define GARROW_ARRAY_GET_PRIVATE(obj)                                   \
  (G_TYPE_INSTANCE_GET_PRIVATE((obj), GARROW_TYPE_ARRAY, GArrowArrayPrivate))

コンストラクターではstd::shard_ptr<arrow::Array>を受け取ってこのインスタンス変数に設定します。(実際はプロパティーを使っているのでもう少し処理が増えています。)

GArrowArray *
garrow_array_new_raw(std::shared_ptr<arrow::Array> *arrow_array)
{
  /* GARROW_ARRAY()はバリデーションつきでGArrowArray *にキャストするマクロ */
  /* GARROW_TYPE_ARRAYはGArrowArray用のGTypeを返すマクロ */
  /* GTypeはGObjectで型情報を表現するデータ */
  auto array = GARROW_ARRAY(g_object_new(GARROW_TYPE_ARRAY, NULL));
  /* GARROW_ARRAY_GET_PRIVATE()は前述のプライベートなデータ用の
     構造体を返すマクロ */
  auto priv = GARROW_ARRAY_GET_PRIVATE(array);
  /* Apache Arrowのオブジェクトの参照を増やす */
  priv->array = *arrow_array;
  return array;
}

デストラクター(finalize())ではコンストラクターで増やした参照を減らします。

static void
garrow_array_finalize(GObject *object)
{
  auto priv = GARROW_ARRAY_GET_PRIVATE(object);
  /* priv->array.reset()でも同じ */
  priv->array = nullptr;
  G_OBJECT_CLASS(garrow_array_parent_class)->finalize(object);
}

まとめ

GObject Introspectionでの「一般的な」オブジェクトの寿命について説明しました。次回は本当に戻り値のオブジェクトの寿命について説明します。