ま、そんなところで。

ニッチな技術系メモとか、車輪を再発明してみたりとか.

C++でMessagePackを使ってみる(3)〜 シリアライズ/デシリアライズの流れ 〜

データ形態を変換する関数

MessagePackはオブジェクトの3つの形態をそれぞれ遷移させることでオブジェクトをシリアライズ/デシリアライズしています.
ここでは形態を変換する関数について整理しておきます.

3つのオブジェクトの形態

MessagePackの処理の過程ではオブジェクトは下記の3形態をとります.

  • msgpack format bytes
    こちらはいわゆるバイトデータ.
    MessagePack形式で完全にシリアライズされたバイト列の状態です.
  • msgpack::object(msgpack_object)
    中間データ形式.
    MessagePackのデータ型とデータ列をセットにしたオブジェクト.
    シリアライズデータへはこのobject型を経由します.
    C++用のmsgpack::objectとC言語用のmsgpack_objectはメモリレイアウトが同じなので, 相互にCopy可能になっているようです.
  • C/C++オブジェクト
    こちらはC/C++のプログラムで扱うオブジェクトの形態.

MessagePackには各々へ変換する関数/メソッドが準備されています.
公式Wikiにもありますが遷移図は以下のような感じです.

C++ Object
C++ Object
msgpack::object
msgpack::object
msgpack format bytes
<div>msgpack format bytes</div>

MessagePackが取り扱える型は、MESSAGE_DEFINEを定義している型 もしくは 対応するアダプタが定義されている型になります.
MessagePackがデフォルトでアダプタ定義している型は下記に一覧があります.
(参照 : predefined adaptors)

シリアライズ操作

C/C++オブジェクト → msgpack format bytes

これは msgpack::pack 関数を使います.
オブジェクトから直接msgpack format bytesへ変換します.
サンプルもこのパターンでシリアライズしているものがほとんど.
内部処理の流れとしては再帰をつかってMESSAGE_DEFINEなどのシリアライズメソッドを巡回し, C++オブジェクトをシーケンシャルに変換していきます.

SomeClass some;
// シリアライズデータ用のバッファ
msgpack::sbuffer buffer;
// シリアライズ
msgpack::pack(buffer, some);

C/C++オブジェクト → msgpack::object 変換

msgpack::object は下記のように定義されていて、9種類のデータとタイプを保持しています.
msgpack::object_array と msgpack::object_map は objectのコンテナになっていて別のobjectを複数保持できます.
つまり、どんなC++オブジェクトも最終的には下記の9種類の入れ子状態で表現されることになります.
※ 表現できる型については What is msgpack::object に一覧があります.

struct object {
    union union_type {
        bool boolean;
        uint64_t u64;
        int64_t  i64;
        double   f64;
        msgpack::object_array array;
        msgpack::object_map map;
        msgpack::object_str str;
        msgpack::object_bin bin;
        msgpack::object_ext ext;
    };
    msgpack::type::object_type type; // タイプ
    union_type via; // データ
       :
};

boolean, u64, i64, f64 で表現可能な真偽型(bool)と数値型(int, unsigned int, float, double, etc..)を msgpack::objectへ変換するには、 objectのテンプレートコンストラクタにC++オブジェクトを引数にして初期化するだけでOK.
うまい具合に処理してくれます.

int64_t hoge;
msgpack::object obj(hoge);

C++クラスのインスタンスのような複雑なものは union_type 単体では表現できないため、 別の参照用メモリ領域として msgpack::zone を与えるオーバーロード版のテンプレートコンストラクタ msgpack::object(T& obj, zone& z) を利用します.
msgpack:object コンストラクタ内では zone上に参照用のメモリ領域を確保してデータを配置し、 zone上の領域を参照するように msgpack:object を生成してくれます.

Hoge someObj;
msgpack::zone z;
msgpack::object obj(someObj, z); // someObjをmsgpack::object化したもの.

msgpack::object → msgpack format bytes 変換

msgpack::pack には msgpack::object 型を引数にとるオーバーライドがあり、 msgpack::object の内容をmsgpack format bytesへ変換します.
ただし、msgpack::objectが zone を参照している場合、msgpack::object が msgpack::packでバイト変換されるまでは zone は有効でなければなりません.
zoneの生存期間に注意が必要です.

msgpack::zone z; // メモリ領域zone
std::string strobj("StringDataです");
// stringをobject化したもの.
msgpack::object obj = msgpack::object(strobj, z)
// シリアライズデータ用のバッファ
msgpack::sbuffer buffer;
// シリアライズ
msgpack::pack(buffer, obj); // ここまでは z は有効でなければならない.

シリアライズ操作

msgpack format bytes → msgpack::object 変換

msgpack::unpack で msgpack::objectへ復元します.
unpackするとobjectはobject_handleというオブジェクトが取得できます.

// シリアライズデータ
char* pdata   = buffer.data();
size_t data_size   = buffer.size();
// msgpack format bytes → msgpac::objectの復元
// object_handleはobjectとzoneを管理するオブジェクト.
msgpack::object_handle hd = msgpack::unpack(pdata, data_size);

object_handle は object と zone をまとめて管理するクラスで、以下のように定義されています.
zoneは、objが参照するデータを配置したメモリ領域です.
シリアライズのときは特に気にする必要はありません.

class object_handle {
      :
      :
private:
  msgpack::object m_obj;
  msgpack::unique_ptr<msgpack::zone> m_zone; // m_objが外部参照するためのメモリ領域
};

object_handle内にあるprivateメンバのobjectへのアクセスは、get()で参照を取得して行います.

// msgpack::objectを読み出す
msgpack::object_handle hd = msgpack::unpack(pdata, data_size);
// getで内部の msgpack::object の参照を取得.
const msgpack::object& obj = hd.get();

msgpack::object → C++オブジェクト 変換

objectからC++オブジェクトへ復元するには、object のメンバテンプレート関数である convert もしくは as を使います.
どちらも同じオブジェクトの復元を行うメソッドですが、convertはoutタイプの参照パラメータ、asはreturnで復元したオブジェクトを返却します.

convert もしくは as で C++オブジェクトへの復元が完了するまでは object_handle が スコープアウトしないように注意.

// シリアライズデータを読み出す
msgpack::object_handle hd = msgpack::unpack(pdata, data_size);
const msgpack::object& obj = hd.get();
// デシリアライズ
Derived restored;
// (1) convert<T>を使う場合
obj.convert(restored);
// (2) as<T>を使う場合
restored = obj.as<Derived>();

ちなみに、msgpack::object_handle には operator *operator -> が定義されていて、 object_handle 内部の msgpack::object のメソッドを直接呼び出せるようになっていたりします.

Derived restored;
msgpack::object_handle hd = msgpack::unpack(pdata, data_size);
// operator* でobjectのメソッドを使用する
(*hd).convert(restored);
// operator -> でasをコール
restored = hd->as<Derived>();

公式を含めて出回っているサンプルではあまり見かけない例ですが、こっちのほうがよりスマートかもですね.


参考記事