cereal

ライブラリシリアライゼーションC++11ヘッダーオンリーJSONXMLバイナリ

ライブラリ

cereal

概要

cerealは、C++11用のヘッダーオンリーシリアライゼーションライブラリです。任意のデータ型を、コンパクトなバイナリエンコーディング、XML、JSONなどの異なる表現形式に可逆的に変換できます。Boostのシリアライゼーションライブラリに慣れた開発者にとって親しみやすい構文を採用しながら、より軽量で高速、そして拡張しやすい設計となっています。

詳細

cerealは、外部依存関係を持たず、最新のC++11テンプレートプログラミング技術を活用して設計されています。シリアライゼーションとデシリアライゼーションに同じコードを使用する「2-in-1」宣言的制御フローを採用し、コードの重複を最小限に抑えています。

主な特徴:

  • 複数のアーカイブ形式: バイナリ、ポータブルバイナリ、JSON、XMLアーカイブをサポート
  • 非侵入的シリアライゼーション: 既存のクラスを変更せずにシリアライゼーション可能
  • STLコンテナサポート: 標準ライブラリのほぼすべての型に対する組み込みサポート
  • スマートポインタ対応: std::shared_ptrstd::unique_ptrの完全サポート
  • 継承とポリモーフィズム: 完全な継承とポリモーフィズムのサポート
  • バージョニング: クラス定義の変更に対応するバージョニング機能
  • Name-Value Pairs (NVP): テキストベースアーカイブでの読みやすい出力

シリアライゼーション方法の柔軟性:

  • クラス内のserialize()メンバー関数
  • 別々のsave()load()メンバー関数
  • 非メンバー関数による外部定義
  • コンパイル時に適切な方法を自動検出

メリット・デメリット

メリット

  • ヘッダーオンリー: コンパイルやリンクが不要で、プロジェクトへの統合が簡単
  • 外部依存なし: 他のライブラリに依存せず、単独で動作
  • 高速: Boost.Serializationより高速で、メモリ効率も良い
  • 使いやすい構文: Boostに似た直感的な構文で、学習コストが低い
  • 柔軟な拡張性: カスタムアーカイブタイプの作成が容易
  • モダンC++: C++11の機能を活用した、クリーンで効率的な実装
  • 充実したドキュメント: 詳細なドキュメントとサンプルコード
  • アクティブな開発: USCiLab(南カリフォルニア大学)により継続的にメンテナンス

デメリット

  • C++11必須: C++11以降のコンパイラが必要(GCC 4.7.3、Clang 3.3、MSVC 2013以降)
  • 生ポインタ非対応: 安全性のため、生ポインタと参照はサポートしない(スマートポインタのみ)
  • サイズ最適化なし: バイナリフォーマットは速度優先で、必ずしも最小サイズではない
  • スキーマレス: プロトコルバッファのようなスキーマ定義はない

参考ページ

書き方の例

基本的なシリアライゼーション

#include <cereal/archives/binary.hpp>
#include <cereal/types/unordered_map.hpp>
#include <cereal/types/memory.hpp>
#include <cereal/types/vector.hpp>
#include <fstream>

struct MyRecord {
    uint8_t x, y;
    float z;
    
    template <class Archive>
    void serialize(Archive & ar) {
        ar(x, y, z);
    }
};

struct SomeData {
    int32_t id;
    std::shared_ptr<std::unordered_map<uint32_t, MyRecord>> data;
    
    template <class Archive>
    void save(Archive & ar) const {
        ar(data);
    }
    
    template <class Archive>
    void load(Archive & ar) {
        static int32_t idGen = 0;
        id = idGen++;
        ar(data);
    }
};

int main() {
    // データの保存
    {
        std::ofstream os("out.cereal", std::ios::binary);
        cereal::BinaryOutputArchive archive(os);
        
        SomeData myData;
        myData.data = std::make_shared<std::unordered_map<uint32_t, MyRecord>>();
        (*myData.data)[1] = {1, 2, 3.5f};
        
        archive(myData);
    }
    
    // データの読み込み
    {
        std::ifstream is("out.cereal", std::ios::binary);
        cereal::BinaryInputArchive archive(is);
        
        SomeData myData;
        archive(myData);
    }
    
    return 0;
}

JSONアーカイブの使用

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

struct Person {
    std::string name;
    int age;
    std::vector<std::string> hobbies;
    
    template<class Archive>
    void serialize(Archive & ar) {
        ar(CEREAL_NVP(name),
           CEREAL_NVP(age),
           CEREAL_NVP(hobbies));
    }
};

int main() {
    Person person{"太郎", 25, {"プログラミング", "読書", "ゲーム"}};
    
    // JSONに書き出し
    std::stringstream ss;
    {
        cereal::JSONOutputArchive archive(ss);
        archive(cereal::make_nvp("person", person));
    }
    
    std::cout << ss.str() << std::endl;
    
    // JSONから読み込み
    Person loaded;
    {
        cereal::JSONInputArchive archive(ss);
        archive(cereal::make_nvp("person", loaded));
    }
    
    return 0;
}

非侵入的シリアライゼーション

#include <cereal/archives/xml.hpp>

// 既存のクラス(変更不可)
class ExternalClass {
public:
    int getValue() const { return value; }
    void setValue(int v) { value = v; }
private:
    int value;
};

// 非メンバー関数でシリアライゼーションを定義
template<class Archive>
void serialize(Archive & ar, ExternalClass & obj) {
    int value = obj.getValue();
    ar(value);
    if (Archive::is_loading::value) {
        obj.setValue(value);
    }
}

int main() {
    ExternalClass obj;
    obj.setValue(42);
    
    std::stringstream ss;
    {
        cereal::XMLOutputArchive archive(ss);
        archive(cereal::make_nvp("external_object", obj));
    }
    
    std::cout << ss.str() << std::endl;
    return 0;
}

ポリモーフィックタイプのシリアライゼーション

#include <cereal/types/polymorphic.hpp>
#include <cereal/archives/json.hpp>
#include <cereal/types/memory.hpp>

class Animal {
public:
    virtual ~Animal() = default;
    virtual void speak() const = 0;
    
    template<class Archive>
    void serialize(Archive & ar) {
        // 基底クラスのシリアライゼーション
    }
};

class Dog : public Animal {
public:
    Dog() = default;
    Dog(const std::string& n) : name(n) {}
    
    void speak() const override {
        std::cout << name << " says: ワン!" << std::endl;
    }
    
    template<class Archive>
    void serialize(Archive & ar) {
        ar(cereal::base_class<Animal>(this), name);
    }
    
private:
    std::string name;
};

class Cat : public Animal {
public:
    Cat() = default;
    Cat(const std::string& n) : name(n) {}
    
    void speak() const override {
        std::cout << name << " says: ニャー!" << std::endl;
    }
    
    template<class Archive>
    void serialize(Archive & ar) {
        ar(cereal::base_class<Animal>(this), name);
    }
    
private:
    std::string name;
};

// 派生クラスの登録
CEREAL_REGISTER_TYPE(Dog)
CEREAL_REGISTER_TYPE(Cat)

int main() {
    std::vector<std::shared_ptr<Animal>> animals;
    animals.push_back(std::make_shared<Dog>("ポチ"));
    animals.push_back(std::make_shared<Cat>("タマ"));
    
    // シリアライズ
    std::stringstream ss;
    {
        cereal::JSONOutputArchive archive(ss);
        archive(animals);
    }
    
    std::cout << ss.str() << std::endl;
    
    // デシリアライズ
    std::vector<std::shared_ptr<Animal>> loaded_animals;
    {
        cereal::JSONInputArchive archive(ss);
        archive(loaded_animals);
    }
    
    for (const auto& animal : loaded_animals) {
        animal->speak();
    }
    
    return 0;
}

バージョニングのサポート

#include <cereal/archives/binary.hpp>
#include <cereal/types/string.hpp>

class VersionedClass {
public:
    std::string name;
    int value;
    std::string new_field; // バージョン1で追加
    
    template<class Archive>
    void serialize(Archive & ar, const uint32_t version) {
        ar(name, value);
        
        if (version >= 1) {
            ar(new_field);
        }
    }
};

// クラスのバージョンを指定
CEREAL_CLASS_VERSION(VersionedClass, 1)

int main() {
    VersionedClass obj{"テスト", 100, "新しいフィールド"};
    
    // 保存と読み込み
    std::stringstream ss;
    {
        cereal::BinaryOutputArchive oarchive(ss);
        oarchive(obj);
    }
    
    VersionedClass loaded;
    {
        cereal::BinaryInputArchive iarchive(ss);
        iarchive(loaded);
    }
    
    return 0;
}

カスタムタイプのシリアライゼーション

#include <cereal/archives/json.hpp>
#include <chrono>

namespace cereal {
    // std::chrono::time_pointのシリアライゼーション
    template<class Archive, class Clock, class Duration>
    void save(Archive & ar, const std::chrono::time_point<Clock, Duration> & tp) {
        ar(tp.time_since_epoch().count());
    }
    
    template<class Archive, class Clock, class Duration>
    void load(Archive & ar, std::chrono::time_point<Clock, Duration> & tp) {
        typename Duration::rep count;
        ar(count);
        tp = std::chrono::time_point<Clock, Duration>(Duration(count));
    }
}

struct Event {
    std::string name;
    std::chrono::system_clock::time_point timestamp;
    
    template<class Archive>
    void serialize(Archive & ar) {
        ar(CEREAL_NVP(name), CEREAL_NVP(timestamp));
    }
};

int main() {
    Event event{"サンプルイベント", std::chrono::system_clock::now()};
    
    std::stringstream ss;
    {
        cereal::JSONOutputArchive archive(ss);
        archive(event);
    }
    
    std::cout << ss.str() << std::endl;
    return 0;
}