cereal

libraryserializationC++11header-onlyJSONXMLbinary

Library

cereal

Overview

cereal is a header-only C++11 serialization library. It can reversibly convert arbitrary data types into different representations such as compact binary encodings, XML, or JSON. While adopting a syntax familiar to developers experienced with Boost's serialization library, it's designed to be lighter, faster, and easier to extend.

Details

cereal is designed with no external dependencies, leveraging modern C++11 template programming techniques. It adopts a "2-in-1" declarative control flow that uses the same code for both serialization and deserialization, minimizing code duplication.

Key features:

  • Multiple Archive Formats: Supports binary, portable binary, JSON, and XML archives
  • Non-Intrusive Serialization: Can serialize existing classes without modification
  • STL Container Support: Built-in support for almost all standard library types
  • Smart Pointer Support: Full support for std::shared_ptr and std::unique_ptr
  • Inheritance and Polymorphism: Complete support for inheritance and polymorphism
  • Versioning: Version support for handling changes in class definitions
  • Name-Value Pairs (NVP): Readable output for text-based archives

Flexible serialization methods:

  • serialize() member function within the class
  • Separate save() and load() member functions
  • External definition via non-member functions
  • Automatic detection of the appropriate method at compile time

Pros and Cons

Pros

  • Header-Only: No compilation or linking required, easy project integration
  • No External Dependencies: Works standalone without other libraries
  • Fast: Faster than Boost.Serialization with better memory efficiency
  • Easy-to-Use Syntax: Intuitive syntax similar to Boost, low learning curve
  • Flexible Extensibility: Easy to create custom archive types
  • Modern C++: Clean and efficient implementation using C++11 features
  • Extensive Documentation: Detailed documentation and sample code
  • Active Development: Continuously maintained by USCiLab (University of Southern California)

Cons

  • C++11 Required: Needs C++11 or later compiler (GCC 4.7.3, Clang 3.3, MSVC 2013 or newer)
  • No Raw Pointer Support: For safety, doesn't support raw pointers and references (smart pointers only)
  • No Size Optimization: Binary format prioritizes speed, not necessarily minimal size
  • Schemaless: No schema definition like protocol buffers

References

Code Examples

Basic Serialization

#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() {
    // Saving data
    {
        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);
    }
    
    // Loading data
    {
        std::ifstream is("out.cereal", std::ios::binary);
        cereal::BinaryInputArchive archive(is);
        
        SomeData myData;
        archive(myData);
    }
    
    return 0;
}

Using JSON Archives

#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{"John Doe", 25, {"programming", "reading", "gaming"}};
    
    // Write to JSON
    std::stringstream ss;
    {
        cereal::JSONOutputArchive archive(ss);
        archive(cereal::make_nvp("person", person));
    }
    
    std::cout << ss.str() << std::endl;
    
    // Read from JSON
    Person loaded;
    {
        cereal::JSONInputArchive archive(ss);
        archive(cereal::make_nvp("person", loaded));
    }
    
    return 0;
}

Non-Intrusive Serialization

#include <cereal/archives/xml.hpp>

// Existing class (cannot be modified)
class ExternalClass {
public:
    int getValue() const { return value; }
    void setValue(int v) { value = v; }
private:
    int value;
};

// Define serialization with non-member function
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;
}

Polymorphic Type Serialization

#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) {
        // Base class serialization
    }
};

class Dog : public Animal {
public:
    Dog() = default;
    Dog(const std::string& n) : name(n) {}
    
    void speak() const override {
        std::cout << name << " says: Woof!" << 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: Meow!" << std::endl;
    }
    
    template<class Archive>
    void serialize(Archive & ar) {
        ar(cereal::base_class<Animal>(this), name);
    }
    
private:
    std::string name;
};

// Register derived classes
CEREAL_REGISTER_TYPE(Dog)
CEREAL_REGISTER_TYPE(Cat)

int main() {
    std::vector<std::shared_ptr<Animal>> animals;
    animals.push_back(std::make_shared<Dog>("Buddy"));
    animals.push_back(std::make_shared<Cat>("Whiskers"));
    
    // Serialize
    std::stringstream ss;
    {
        cereal::JSONOutputArchive archive(ss);
        archive(animals);
    }
    
    std::cout << ss.str() << std::endl;
    
    // Deserialize
    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;
}

Versioning Support

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

class VersionedClass {
public:
    std::string name;
    int value;
    std::string new_field; // Added in version 1
    
    template<class Archive>
    void serialize(Archive & ar, const uint32_t version) {
        ar(name, value);
        
        if (version >= 1) {
            ar(new_field);
        }
    }
};

// Specify class version
CEREAL_CLASS_VERSION(VersionedClass, 1)

int main() {
    VersionedClass obj{"Test", 100, "New field"};
    
    // Save and load
    std::stringstream ss;
    {
        cereal::BinaryOutputArchive oarchive(ss);
        oarchive(obj);
    }
    
    VersionedClass loaded;
    {
        cereal::BinaryInputArchive iarchive(ss);
        iarchive(loaded);
    }
    
    return 0;
}

Custom Type Serialization

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

namespace cereal {
    // Serialization for 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{"Sample Event", std::chrono::system_clock::now()};
    
    std::stringstream ss;
    {
        cereal::JSONOutputArchive archive(ss);
        archive(event);
    }
    
    std::cout << ss.str() << std::endl;
    return 0;
}