MessagePackでIDLを書く
MessagePackでプロトコルを定義する。言わばIDLを書く機能。
#include <msgpack.hpp> // プロトコル定義とか namespace protocol { using msgpack::define; using namespace msgpack::type; // msgpack::define<T>を継承する。 // - msgpack::object型から直接型変換できる // - 直接シリアライズできる // - メンバ関数を自由に定義できる struct Get : define< tuple<raw_ref, uint64_t> > { Get() {} Get(const char* key, size_t keylen, uint64_t cas) : define_type(msgpack_type( raw_ref(key,keylen), cas )) {} const char* key() const { return get<0>().ptr; } size_t keylen() const { return get<0>().size; } uint64_t cas() const { return get<1>(); } }; // ネストもできる struct AsyncGet : define< std::vector<Get> > { AsyncSet() {} void add(const Get& s) { push_back(s); } }; }
シリアライズ・デシリアライズって、まぁやればできるのだけど、ポインタの計算やらhtonsやら*1、定型の処理ばかりで面倒な部類に入る。
そこのところをライブラリに任せられれば楽なのだけど、コレだっ!というのが無かったので、MessagePackを作ってみた。
しかしライブラリにありがちな話で、楽なのだけど自由度が制限されるために「このケースでは使いにくい」とか「もっと効率的にシリアライズできるハズなのにできない」ということがある。MessagePackではそういう問題をできるだけ排除したい。
というわけでこの機能を使えば、簡単にシリアライズ・デシリアライズ用のインターフェイスを作れる。主にプロトコルの定義に使える。
ちなみにmsgpack::defineクラスはvirtualなメンバ関数を持たないので、継承してもオーバーヘッドは発生しない。
既存のデータ構造をシリアライズしたい!というときには、msgpack::defineを継承するのはイヤだし、そもそもシリアライズ時にデータ構造を変換することができない(常にすぐさまシリアライズ可能なデータ構造を保持していないといけない)ので、この機能は適さない。そのときはメンバ関数に msgpack_pack() と msgpack_unpack() を定義するやり方を使う。
それよりもっとトリッキーな実装をしたい場合は、グローバルな operator>> や operator<< を定義すれば、もっと直接的に操作することができる。
たとえばバイト列をシリアライズするとき、先頭にメタデータを付けたバイト列を送りたいかも知れない*2。データ本体が巨大になる可能性があるとき、先頭にメタデータを付けようとするとメタデータ分だけ大きいサイズのメモリを確保し直してコピーしするのはイヤになる。メモリ管理も面倒になるし。
しかしシリアライズ処理中に不連続なアドレスを一本のバイト列に変換している*3わけで、あらかじめ先頭にメタデータをくっつけたメモリを用意しないといけないというのは、ライブラリのAPIの都合でしかない。MessagePackの標準機能では実現できないが、プログラマが定義すれば実現可能な仕組みを用意している。
バッファリングもプログラマがカスタマイズできる。データをシリアライズしてファイルに書き出したいとき、シリアライズ結果をmmap()した領域に直接書き出したくなる。勝手にmallocしちゃうライブラリだと困るが、MessagePackなら直接書き出せる。
バッファリングしないバッファクラスを作れば、出力先がバッファリング機能を備えているときに二重にバッファリングすることを防げる。
そんなこんなで、MessagePackは結構自信作である ^_^;)