cereal
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_ptrandstd::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()andload()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;
}