Serialization Functions
=======================
Since C++ lacks
reflection,
implementing serialization requires you to specify which data members
should be serialized. cereal provides a number of ways to specify such
serialization functions for your classes, even if you don't have access to
their internals.
---
### TLDR Version
cereal supports single serializaton functions (`serialize`) or split load/save pairs (`load` and `save`) either inside
or outside of classes. Load and save functions can optionally be designated as minimal (`load_minimal` and
`save_minimal`).
Internal serialization functions can be kept private so long as cereal is given access by
befriending `cereal::access`. You can optionally choose to store versioning information by adding an additional
parameter to your serialization functions (`std::uint32_t const version`) and using the `CEREAL_CLASS_VERSION` macro.
cereal will tell you if you've made a mistake at compile time, if possible.
---
## Types of Serialization Functions
Serialization functions can either be internal or external. Functionality can
either be in a single `serialize` function, or a split `load` and `save`
functions. Load and save functions can optionally be made to emit minimal representations,
as detailed [later](#minimal).
When possible, it is preferred to use a single internal `serialize`
method, though split methods can be used when it is necessary (e.g. to
dynamically allocate memory upon loading a class). Unlike Boost, there is no need to explicitly tell cereal that it
needs to use the split load-save pair; cereal will pick whichever is present and give a compile time error if it cannot
disambiguate a single serialization method.
{% capture class_begin %}struct MyClass
{
int x, y, z;{% endcapture %}
Internal serialize function
{% highlight cpp %}
{{ class_begin }}
template
void serialize(Archive & archive)
{
archive( x, y, z );
}
};
{% endhighlight %}
Internal split/load functions
{% highlight cpp %}
{{ class_begin }}
template
void save(Archive & archive) const
{
archive( x, y, z );
}
template
void load(Archive & archive)
{
archive( x, y, z );
}
};{% endhighlight %}
External serialize function
{% highlight cpp %}
{{ class_begin }}
};
template
void serialize(Archive & archive,
MyClass & m)
{
archive( m.x, m.y, m.z );
}
{% endhighlight %}
External split/load functions
{% highlight cpp %}
{{ class_begin }}
};
template
void save(Archive & archive,
MyClass const & m)
{
archive( m.x, m.y, m.z );
}
template
void load(Archive & archive,
MyClass & m)
{
archive( m.x, m.y, m.z );
} {% endhighlight %}
Important! Save functions are **const** (or take a **const** reference to your class). cereal will throw a static assertion if it detects a non const save function.
External serialization functions should be placed either in the same namespace as the types they serialize or in the
`cereal` namespace so that the compiler can find them properly.
### Non-public serialization
Serialization functions can be placed under access control to be protected or private. cereal will need access to them,
and can be given access by befriending `cereal::access`, defined in ``:
```cpp
#include
class MyCoolClass
{
private:
int secretX;
friend class cereal::access;
template
void serialize( Archive & ar )
{
ar( secretX );
}
};
```
This also works with split save/load functions.
### Minimal split serialization
In addition to supporting the normal load/save split serialization functions, cereal also supports
`load_minimal` and `save_minimal` serialization functions. These serialization functions follow the same rules as the
others (you may only have one type of serialization per archive-type pair) but have a slightly different interface and
can change output behavior.
Minimal serialization functions are designed to take a type and reduce it to a single primitive or string value. The
primary reason to use minimal serialization is to simplify the output to a human readable archive (e.g. XML or JSON).
Internal split minimal serialization
{% highlight cpp %}
struct MyData
{
double d;
template
double save_minimal(
Archive const & ) const
{
return d;
}
template
void load_minimal( Archive const &,
double const & value )
{
d = value;
}
};
{% endhighlight %}
External split minimal serialization
{% highlight cpp %}
struct MyData
{
double d;
};
template
double save_minimal(
Archive const &,
MyData const & md )
{
return md.d;
}
template
void load_minimal( Archive const &,
MyData & md,
double const & value )
{
md.d = value;
}
{% endhighlight %}
There are a few key differences in the interface of the minimal functions:
1. They accept their `Archive` template parameter by constant reference.
2. `save_minimal` returns the minimal representation, which can either be an arithmetic type or an `std::string`.
3. `load_minimal` is passed a constant reference to the return type of `save_minimal`.
Minimal serialization can only emit a single value and are only useful when this approach makes sense. Consider this
example showing the differences between minimal and standard serialization (serialize or split load/save pair):
```cpp
#include
#include
struct Minimal
{
std::string myData;
template
std::string save_minimal( Archive const & ) const
{ return myData; }
template
void load_minimal( Archive const &, std::string const & value )
{ myData = value; }
};
struct Normal
{
std::string myData;
template
void serialize( Archive & ar )
{ ar( myData ); }
};
int main()
{
Minimal m = {"minimal"};
Normal n = {"normal"};
cereal::JSONOutputArchive ar( std::cout );
ar( CEREAL_NVP(m), CEREAL_NVP(n) );
}
```
which produces the output:
```json
{
"m": "minimal",
"n": {
"value0": "normal"
}
}
```
Note the lack of an internal node for the minimal representation.
If you are using template metaprogramming on your minimal serialization functions, you may find the discussion of
`strip_minimal` in the advanced section of the
[archive specialization]({{ site.baseurl }}/archive_specialization.html#strip_minimal) documentation useful.
### Explicit versioning
cereal supports adding explicit versioning information for types, much like Boost class versioning.
This is optional in cereal and by default is not used for any type. You can choose to use versioning by adding
an additional parameter to your serialization functions (regardless of which serialization style you are using), a `const std::uint32_t`, typically named `version`.
This parameter will always be given the appropriate version number by cereal. When saving data, cereal looks for an
explicit version which you can specify with the `CEREAL_CLASS_VERSION` macro (see the [doxygen docs]({{ site.baseurl }}/assets/doxygen/group__Utility.html) for detailed information). This macro takes a type and a version
number, and causes cereal to serialize this version information when it saves that type using a versioned serialization
function. If you use this macro but do not use a versioned serialization function, no version information will be
saved. If you use a versioned serialization function and do not specify the version number using the macro, cereal will
default to giving a version number of zero.
When performing loads, cereal will load versioning information if your serialization function is versioned. If you did
not use a versioned serialization function to create the archive you are loading from, your data will be corrupted and your program will likely crash. This
loaded version number will be supplied via the version parameter you add to your serialization functions.
Important! Data serialized without versioning cannot be loaded by a versioned
serialization function (and vice versa).
Important! If you choose to use versioning, you must ensure that all serialization functions for a type support the versioning
parameter (both your load and save must have it in the case of split serialization).
Here is a small example of adding versioning to both an internal serialize function as well as an externally split
load/save pair:
```cpp
#include
struct MyCoolClass
{
// xCEREAL will supply the version automatically when loading or saving
// The version number comes from the CEREAL_CLASS_VERSION macro
template
void serialize( Archive & ar, std::uint32_t const version )
{
// You can choose different behaviors depending on the version
// This is useful if you need to support older variants of your codebase
// interacting with newer ones
if( version > some_number )
// do something
else
// do something else
}
};
struct AnotherType { };
// Versioning can be applied to any type of serialization function, but
// if used for a load it must also be used for a save
template
void save( Archive & ar, AnotherType const & at, std::uint32_t const version )
{ }
template
void load( Archive & ar, AnotherType & at, std::uint32_t const version )
{ }
// Associate some type with a version number
CEREAL_CLASS_VERSION( MyCoolClass, 32 );
// If we don't associate a class with a version number and use a versioned
// serialize function, its version number will default to 0
```
### Inheritance
Serialization functions, like any other function, will be inherited by derived classes. Depending on the serialization
method you have chosen to employ, this can cause some ambiguities that cereal is not happy with:
```cpp
#include
#include
struct MyBase
{
template
void serialize( Archive & )
{ }
};
struct MyDerived : MyBase
{
template
void load( Archive & )
{ }
template
void save( Archive & ) const
{ }
};
int main()
{
MyDerived d;
cereal::BinaryOutputArchive ar( std::cout );
ar( d ); // static assertion failure:
// xCEREAL detected both a serialize and save/load pair for MyDerived
}
```
In the above example, `MyDerived` inherits the public `serialize` function from `MyBase`, thus giving it both a
`serialize` and `load`-`save` pair. cereal is unable to disambiguate which it should call and thus gives a compile time
static assertion.
Even if `serialize` was made `private` in the base class, cereal may still have access to it from
the derived class because of a friend declaration to `cereal::access`, resulting in the same error. The solution to this
error is to provide an explicit disambiguation for cereal:
```cpp
#include
namespace cereal
{
template
struct specialize {};
// xCEREAL no longer has any ambiguity when serializing MyDerived
}
// alternatively, you can use a macro at global scope to do the above disambiguation:
// CEREAL_SPECIALIZE_FOR_ALL_ARCHIVES( MyDerived, cereal::specialization::member_load_save )
```
More information can be found by reading the doxygen documentation for `` [here]({{ site.baseurl }}/assets/doxygen/specialize_8hpp.html).
### Default construction
cereal requires access to a default constructor for types it serializes. If you don't want to provide a default
constructor but do want to serialize smart pointers to it, you can get around this restriction using a special overload,
detailed in the [pointers](pointers) section of the documentation.
### Renaming serialization functions
If your coding conventions conflict with the expected names for cereal serialization functions or if they conflict for
any other reason, you can use the macros in `` to change these names. See the [doxygen
documentation]({{ site.baseurl }}/assets/doxygen/macros_8hpp.html) for more information.