Rokiのチラ裏

学生による学習のログ

cereal A C++11 library for serializationをParameterized Base Class Idiomで美味しく頂く

多分既出ネタのような気がする(確認していない)が自分用メモも兼ねてまとめて見る事にした。
cerealとはヘッダーオンリーでシリアライズ、デシリアライズが可能なC++ライブラリである。

cereal is a header-only C++11 serialization library. cereal takes arbitrary data types and reversibly turns them into different representations, such as compact binary encodings, XML, or JSON. cereal was designed to be fast, light-weight, and easy to extend - it has no external dependencies and can be easily bundled with other code or used standalone. - http://uscilab.github.io/cereal/ 引用

Parameterized Base Class Idiomを用いて使ってみる。

#include<iostream>
#include<sstream>
#include<cereal/cereal.hpp>
#include<cereal/archives/json.hpp>

template<class Class>
struct Serializable:public Class{
    using Class::Class;
    
    template<class _U>
    void serialize(_U& archive){archive(CEREAL_NVP(Class::data),CEREAL_NVP(Class::value));}
    
    inline void json_output(std::stringstream& ss,const char* s="root")
    {
        cereal::JSONOutputArchive oarchive(ss);
        oarchive(cereal::make_nvp(s,*this));
    }
    inline void json_input(std::stringstream& ss,const char* s="root")
    {
        cereal::JSONInputArchive iarchive(ss);
        iarchive(cereal::make_nvp(s,*this));
    }
};

struct X{
    X()=default;
    explicit X(const std::string& s,const int& x):data(s),value(x){}
    void print()const{std::cout<<data<<'\n'<<value<<std::endl;}
protected:
    std::string data;
    int value=0;
};

int main()
{
    Serializable<X> x("hoge",42);
    Serializable<X> x_input;
    std::stringstream ss;

    x.json_output(ss);
    std::cout<<ss.str()<<std::endl;
    
    x_input.json_input(ss);
    x_input.print();
}

出力結果。

{
    "root": {
        "Class::data": "hoge",
        "Class::value": 42
    }
}
hoge
42

とても美味しい。
当該シリアライザ/デシリアライザはRAII Idiomに基づいている。即ち、オブジェクトが破棄されるまでストリームの状態が不完全である可能性があるので、一段スコープを加える必要がある。*1
因みに既に定義された型やSTLコンテナなどをシリアライズする場合は、当idiomを用いても正直あんまり美味しく見えない。。抽象プログラミングにおけるセマンティックス的には一応美味しいような気もするが。

#include<iostream>
#include<sstream>
#include<cereal/cereal.hpp>
#include<cereal/archives/json.hpp>

template<class Class>
struct Serializable:public Class{
    using Class::Class;
    
    template<class _U>
    void serialize(_U& archive){archive(CEREAL_NVP(Class::data),CEREAL_NVP(Class::value),CEREAL_NVP(Class::position));}
    
    inline void json_output(std::stringstream& ss,const char* s="root")
    {
        cereal::JSONOutputArchive oarchive(ss);
        oarchive(cereal::make_nvp(s,*this));
    }
};

struct Vector;
template<class Archive>
void serialize(Archive&,const Vector&);

struct Vector{ // 既に定義された型
    Vector()=default;
    Vector(const Vector&)=default;
    explicit Vector(const float& x,const float& y):x(x),y(y){}
private:
    float x=0.0f;
    float y=0.0f;
    template<class Archive> friend void serialize(Archive&,const Vector&);
};

template<class Archive>
void serialize(Archive& archive,const Vector& vec)
{
    archive(cereal::make_nvp("x",vec.x),cereal::make_nvp("y",vec.y));
}

struct X{
    X()=default;
    explicit X(const std::string& s,const int& x,const Vector& vec):data(s),value(x),position(vec){}
protected:
    std::string data;
    int value=0;
    Vector position;
};

int main()
{
    Serializable<X> x("hoge",42,Vector(42.0f,42.0f));
    std::stringstream ss;

    x.json_output(ss);
    std::cout<<ss.str()<<std::endl;
}
{
    "root": {
        "Class::data": "hoge",
        "Class::value": 42,
        "Class::position": {
            "x": 42.0,
            "y": 42.0
        }
    }
}

こちらはSTLコンテナ。

#include<iostream>
#include<sstream>
#include<initializer_list>
#include<cereal/cereal.hpp>
#include<cereal/archives/json.hpp>
#include<cereal/types/vector.hpp>

template<class Class>
struct Serializable:public Class{
    using Class::Class;
    
    template<class _U>
    void serialize(_U& archive){archive(CEREAL_NVP(Class::data),CEREAL_NVP(Class::value),CEREAL_NVP(Class::position),CEREAL_NVP(Class::v));}
    
    inline void json_output(std::stringstream& ss,const char* s="root")
    {
        cereal::JSONOutputArchive oarchive(ss);
        oarchive(cereal::make_nvp(s,*this));
    }
};

struct Vector;
template<class Archive>
void serialize(Archive&,const Vector&);

struct Vector{
    Vector()=default;
    Vector(const Vector&)=default;
    explicit Vector(const float& x,const float& y):x(x),y(y){}
private:
    float x=0.0f;
    float y=0.0f;
    template<class Archive> friend void serialize(Archive&,const Vector&);
};

template<class Archive>
void serialize(Archive& archive,const Vector& vec)
{
    archive(cereal::make_nvp("x",vec.x),cereal::make_nvp("y",vec.y));
}

struct X{
    X()=default;
    explicit X(const std::string& s,const int& x,const Vector& vec,const std::initializer_list<int>& l)
        :data(s),value(x),position(vec),v{l}
    {}
protected:
    std::string data;
    int value=0;
    Vector position;
    std::vector<int> v;
};

int main()
{
    Serializable<X> x("hoge",42,Vector(42.0f,42.0f),{1,2,3,4,5});
    std::stringstream ss;

    x.json_output(ss);
    std::cout<<ss.str()<<std::endl;
}
{
    "root": {
        "Class::data": "hoge",
        "Class::value": 42,
        "Class::position": {
            "x": 42.0,
            "y": 42.0
        },
        "Class::v": [
            1,
            2,
            3,
            4,
            5
        ]
    }
}

結論

使い分け大切。後、ライブラリ自体が使いやすすぎる。

参照

*1:Some archives in cereal can only safely finish flushing their contents upon their destruction. Make sure, especially for output serialization, that your archive is automatically destroyed when you are finished with it. - http://uscilab.github.io/cereal/quickstart.html http://uscilab.github.io/cereal/quickstart.html Serialize your data引用