2011年11月12日土曜日

マクロでserialize関数を自動生成

シリアライズというのは,オブジェクトをバイト列に変換すること.その逆はデシリアライズ.
バイト列はファイルに保存したりネットワーク送信したりできるので,いろいろ便利に使える.
例えば,自作アプリの設定値をXMLフォーマットにシリアライズ/デシリアライズできるようにしておけば外部から簡単に設定値を変更できるし,定期的に最新のステータスをネットワーク送信してモニタリングしたりファイルに書き出してログを残したり,といった感じ.

C++でシリアライズをサポートする代表的なライブラリといえば,boost.serializationだと思う.
boost.serializationで自作クラスや構造体のシリアライズ手順を定義するのには複数のやり方が用意されているのだけど,普通は次のような書き方をする.

#include <iostream>
#include <vector>

#include <boost/cstdint.hpp>

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>

#include <boost/archive/xml_oarchive.hpp>

struct book{
  std::string title;
  std::vector<std::string> authors;
  boost::uint16_t year,pages;
// シリアライズ方法の定義:ここから
private:
  friend class boost::serialization::access;
  template<class Ar>
  void serialize(Ar & ar,const unsigned int ver){
    ar & BOOST_SERIALIZATION_NVP(title)
       & BOOST_SERIALIZATION_NVP(authors)
       & BOOST_SERIALIZATION_NVP(year)
       & BOOST_SERIALIZATION_NVP(pages);
  }
// ここまで
};

int main(int argc, char *argv[])
{
  book mcd;

  mcd.title = "Modern C++ Design";
  mcd.authors.push_back("Andrei Alexandrescu");
  mcd.year = 2001;
  mcd.pages = 337;

  // xmlフォーマットで標準出力
  boost::archive::xml_oarchive oa(std::cout);
  oa << boost::serialization::make_nvp("book",mcd);

  return 0;
}

シリアライズしたいクラスごとにこういうのを書いていけばいいのでお手軽といえばお手軽なのだけど,クラスを定義するたびにつらつらと書いているとやはり面倒に感じてくる.
ということで,次のようなマクロを用意しておくのはどうだろう?

// lazy_serialize.hpp

#define PP_VA_ARGS_SIZE(...)                         \
  PP_VA_ARGS_SIZE_I((__VA_ARGS__,25,                 \
                     24,23,22,21,20,19,18,17,        \
                     16,15,14,13,12,11,10,9,         \
                     8,7,6,5,4,3,2,1))

#define PP_VA_ARGS_SIZE_I(tuple) PP_VA_ARGS_SIZE_II tuple

#define PP_VA_ARGS_SIZE_II(_1,_2,_3,_4,_5,_6,_7,_8,             \
                           _9,_10,_11,_12,_13,_14,_15,_16,      \
                           _17,_18,_19,_20,_21,_22,_23,_24,     \
                           _25,size,...) size

#define PP_SERIALIZATION_NVP(z,count,array)                      \
  & BOOST_SERIALIZATION_NVP(BOOST_PP_ARRAY_ELEM(count,array))

#define LAZY_SERIALIZE(...)                               \
  private:                                                \
  friend class boost::serialization::access;              \
  template<class Ar>                                      \
  void serialize(Ar & ar,const unsigned int){             \
    ar LAZY_SERIALIZE_I((__VA_ARGS__));                   \
  }

#define LAZY_SERIALIZE_I(tuple)                              \
  LAZY_SERIALIZE_II(( PP_VA_ARGS_SIZE tuple,                 \
                      PP_SERIALIZATION_NVP,                  \
                      (PP_VA_ARGS_SIZE tuple,tuple) ))

#define LAZY_SERIALIZE_II(tuple) BOOST_PP_REPEAT tuple
#include "lazy_serialize.hpp"

/* 略 */

struct book{
  std::string title;
  std::vector<std::string> authors;
  boost::uint16_t year,pages;
  LAZY_SERIALIZE(title,authors,year,pages);
};

/* 略 */

紋切り型のシリアライズ定義が省略されて,シリアライズしたいメンバを列挙すればOKになる.
ただし,マクロ内に含まれるprivateタグが後続の属性に伝搬してしまうので,クラス定義の末尾に書くのがベター.

0 件のコメント:

コメントを投稿