読者です 読者をやめる 読者になる 読者になる

MessagePack for C++ template版

※2009-03-01追記:ここの内容はもう古いです。最新のドキュメントを参照してください:http://msgpack.sourceforge.jp/cpp:doc


先日MessagePack for C++を紹介したのですが、やはり動的型とC++は合わないことが分かったので、動的型を使う方針はやめました。
しかし IDL + コードジェネレータ というパターンは、IDLを書くというプロセスが面倒(それからProtocol Buffersなどとは差別化を図りたい)ので避けたい。

そこで、templateを駆使し、型をプログラム中に直接記述して静的型に変換できるライブラリを実装しました。↓こんな感じで使えます。

#include <msgpack.hpp>
#include <iostream>
#include <sstream>

int main()
{
  // サンプルデータ
  // 内容は [true, "get", [1,2,3], 10]
  char data[] = {
    0x94,
      0xc3,
      0xa3,
        0x67, 0x65, 0x74,
      0x93,
        0x1, 0x2, 0x3,
      0xa };

  // とりあえずmsgpack::object型にデシリアライズする
  msgpack::zone z;
  msgpack::object obj = msgpack::unpack(data, sizeof(data), z);

  // 表示してみる
  std::cout << "test: " << obj << std::endl;

  // msgpack::object型から静的型に変換する
  msgpack::type::tuple<
    bool,
    std::string,
    std::vector<int>,
    uint32_t
  > request;
  msgpack::convert(request, obj);

  // 静的型からシリアライズしてバイト列にする
  std::stringstream output;
  msgpack::pack(output, request);

  // もう一度デシリアライズして表示してみる
  std::string str = output.str();
  msgpack::object reobj = msgpack::unpack(str.data(), str.size(), z);
  std::cout << "ok? : " << reobj << std::endl;

  return 0;
}

// 実行結果:
// test: [true, "get", [1, 2, 3], 10]
// ok? : [true, "get", [1, 2, 3], 10]

OK。動いています。

ポイントは、msgpack::convert(request, obj);という関数です。msgpack::object(タグ付きのunion)から、msgpack::type::tuple<...>*1という型に変換しています。(変換できなかったら msgpack::type_error 例外(std::bad_castを継承している)が発生します)


ここで、やはり msgpack::type::tuple<...> と言う名前は分かりにくい、アクセサメソッドが付いたクラスを使いたい、と思うかもしれません。
そこで、適当なクラスにvoid msgpack_unpack(msgpack::type::tuple<...>);という名前の関数を実装しておくと、そのクラスに変換できるようになります。
また、msgpack::type::tuple<...> msgpack_pack() const;という名前の関数を実装しておくと、そのクラスからシリアライズすることもできます。

#include <msgpack.hpp>
#include <iostream>
#include <sstream>

// マイクラス
class request_t {
public:
  // デシリアライズ用関数
  void msgpack_unpack( msgpack::type::tuple<
        bool,
        std::string,
        std::vector<int>,
        uint32_t> t )
  {
    m_is_notify = t.get<0>();
    m_method    = t.get<1>();
    m_params    = t.get<2>();
    m_id        = t.get<3>();
  }

  // シリアライズ用関数
  msgpack::type::tuple<
    bool,
    std::string,
    std::vector<int>,
    uint32_t> msgpack_pack() const
  {
    return msgpack::type::make_tuple(m_is_notify, m_method, m_params, m_id);
  }

private:
  bool             m_is_notify;
  std::string      m_method;
  std::vector<int> m_params;
  uint32_t         m_id;
};

int main(void)
{
  // サンプルデータ
  // 内容は [true, "get", [1,2,3], 10]
  char data[] = {
    0x94,
      0xc3,
      0xa3,
        0x67, 0x65, 0x74,
      0x93,
        0x1, 0x2, 0x3,
      0xa };

  // デシリアライズする
  msgpack::zone z;
  msgpack::object obj = msgpack::unpack(data, sizeof(data), z);

  // マイクラスに変換する
  request_t request;
  msgpack::convert(request, obj);

  // マイクラスからシリアライズする
  std::stringstream output;
  msgpack::pack(output, request);

  // もう一度デシリアライズして表示してみる
  std::string str = output.str();
  msgpack::object reobj = msgpack::unpack(str.data(), str.size(), z);
  std::cout << "ok? : " << reobj << std::endl;

  return 0;
}

// 実行結果
// ok? : [true, "get", [1, 2, 3], 10]

というわけで動きます。少なくともgcc-4.0でコンパイルできることを確認しました ;- )


今のところ以下の型は標準でサポートしています:

  • msgpack::type::nil
  • bool
  • unsigned/signed int, short, long, long long(オーバーフローする場合はmsgpack::type_error例外)
  • float, double
  • std::vector
  • std::map, std::multimap
  • msgpack::type::assoc_vector(std::vector< std::pair >のtypedef。ソート済み配列によるmultimap)
  • std::string(中身をコピーするバイナリ列)
  • msgpack::type::raw_ref(中身を参照で保持するバイナリ列)
  • msgpack::type::tuple<...>(16個まで)

クラスにメンバ関数を追加できない場合(組み込み型、既存のいじれないクラスなど)は、Type& operator<< (Type&, msgpack::type::object); や、const Type& operator>> (const Type&, packer); という関数をグローバルに定義してやれば、シリアライズ/デシリアライズできます。


速度が気になるところですが…テストしていません。というのも、どういうデータを使ったテストが現実的なのかよく分からないからです。
msgpack::object型までデシリアライズする速度(静的型に変換していない)は、とりあえずバイナリシリアライズ形式「MessagePack」で数値をデシリアライズした結果と比べてみたところ、60%程度の性能が出ています(文字列の方は誤差が大きすぎて何とも言えない…)。
なぜ先のベンチマークの方が速いのかというと、あのベンチマークではオブジェクトを生成していないからです。全部NULLを返しているので速い。
一方シリアライズをC版とほぼ同じ方法でテストしてみたところ、160%程度の性能が出ています。C++版の方が速いのは、おそらくシリアライズ関数がインライン化されているからです。


MessagePackのソースコードCodeRepos://lang/c/msgpackにあります。チェックアウトしたあと、./bootstrapを実行し、./configure && make && make installでインストールできます。msgpack.hppをincludeし、libmsgpackをリンクしてください。

*1:msgpack::type::tuple<>は、[http://www.boost.org/doc/libs/1_35_0/libs/tuple/doc/tuple_users_guide.html:title=boost::tuple]となんとなく同じです。tuple.get<0>()で0番目の変数が取り出せます。