mirror of
https://github.com/USCiLab/cereal.git
synced 2025-07-21 02:22:01 +02:00
442 lines
13 KiB
Markdown
442 lines
13 KiB
Markdown
Serialization Functions
|
|
=======================
|
|
|
|
Since C++ lacks
|
|
<a href="http://en.wikipedia.org/wiki/Reflection_(computer_programming)">reflection</a>,
|
|
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.
|
|
|
|
<br/>
|
|
{% capture class_begin %}struct MyClass
|
|
{
|
|
int x, y, z;{% endcapture %}
|
|
|
|
|
|
<div class="row">
|
|
<div class="span1"></div>
|
|
<div class="span5">
|
|
|
|
Internal serialize function
|
|
|
|
{% highlight cpp %}
|
|
{{ class_begin }}
|
|
|
|
template<class Archive>
|
|
void serialize(Archive & archive)
|
|
{
|
|
archive( x, y, z );
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
{% endhighlight %}
|
|
</div>
|
|
<div class="span5">
|
|
|
|
Internal split/load functions
|
|
|
|
{% highlight cpp %}
|
|
{{ class_begin }}
|
|
|
|
template<class Archive>
|
|
void save(Archive & archive) const
|
|
{
|
|
archive( x, y, z );
|
|
}
|
|
|
|
template<class Archive>
|
|
void load(Archive & archive)
|
|
{
|
|
archive( x, y, z );
|
|
}
|
|
};{% endhighlight %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="span1"></div>
|
|
<div class="span5">
|
|
|
|
External serialize function
|
|
|
|
{% highlight cpp %}
|
|
{{ class_begin }}
|
|
};
|
|
|
|
template<class Archive>
|
|
void serialize(Archive & archive,
|
|
MyClass & m)
|
|
{
|
|
archive( m.x, m.y, m.z );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{% endhighlight %}
|
|
</div>
|
|
|
|
<div class="span5">
|
|
|
|
External split/load functions
|
|
|
|
{% highlight cpp %}
|
|
{{ class_begin }}
|
|
};
|
|
|
|
template<class Archive>
|
|
void save(Archive & archive,
|
|
MyClass const & m)
|
|
{
|
|
archive( m.x, m.y, m.z );
|
|
}
|
|
|
|
template<class Archive>
|
|
void load(Archive & archive,
|
|
MyClass & m)
|
|
{
|
|
archive( m.x, m.y, m.z );
|
|
} {% endhighlight %}
|
|
</div>
|
|
</div>
|
|
<br/>
|
|
|
|
<span class="label label-warning">Important!</span> 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 `<cereal/access.hpp>`:
|
|
|
|
```cpp
|
|
#include <cereal/access.hpp>
|
|
|
|
class MyCoolClass
|
|
{
|
|
private:
|
|
int secretX;
|
|
|
|
friend class cereal::access;
|
|
|
|
template <class Archive>
|
|
void serialize( Archive & ar )
|
|
{
|
|
ar( secretX );
|
|
}
|
|
};
|
|
```
|
|
|
|
This also works with split save/load functions.
|
|
|
|
<a name="minimal"></a>
|
|
|
|
### 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).
|
|
|
|
<div class="row">
|
|
<div class="span1"></div>
|
|
<div class="span5">
|
|
Internal split minimal serialization
|
|
|
|
{% highlight cpp %}
|
|
struct MyData
|
|
{
|
|
double d;
|
|
|
|
template <class Archive>
|
|
double save_minimal(
|
|
Archive const & ) const
|
|
{
|
|
return d;
|
|
}
|
|
|
|
template <class Archive>
|
|
void load_minimal( Archive const &,
|
|
double const & value )
|
|
{
|
|
d = value;
|
|
}
|
|
|
|
|
|
};
|
|
{% endhighlight %}
|
|
</div>
|
|
<div class="span5">
|
|
|
|
External split minimal serialization
|
|
|
|
{% highlight cpp %}
|
|
struct MyData
|
|
{
|
|
double d;
|
|
};
|
|
|
|
template <class Archive>
|
|
double save_minimal(
|
|
Archive const &,
|
|
MyData const & md )
|
|
{
|
|
return md.d;
|
|
}
|
|
|
|
template <class Archive>
|
|
void load_minimal( Archive const &,
|
|
MyData & md,
|
|
double const & value )
|
|
{
|
|
md.d = value;
|
|
}
|
|
{% endhighlight %}
|
|
</div>
|
|
</div>
|
|
|
|
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 <cereal/archives/json.hpp>
|
|
#include <iostream>
|
|
|
|
struct Minimal
|
|
{
|
|
std::string myData;
|
|
|
|
template <class Archive>
|
|
std::string save_minimal( Archive const & ) const
|
|
{ return myData; }
|
|
|
|
template <class Archive>
|
|
void load_minimal( Archive const &, std::string const & value )
|
|
{ myData = value; }
|
|
};
|
|
|
|
struct Normal
|
|
{
|
|
std::string myData;
|
|
|
|
template <class Archive>
|
|
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.
|
|
|
|
<a name="versioning"></a>
|
|
|
|
### 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.
|
|
|
|
<span class="label label-warning">Important!</span> Data serialized without versioning cannot be loaded by a versioned
|
|
serialization function (and vice versa).
|
|
|
|
<span class="label label-warning">Important!</span> 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 <cereal/cereal.hpp>
|
|
|
|
struct MyCoolClass
|
|
{
|
|
// xCEREAL will supply the version automatically when loading or saving
|
|
// The version number comes from the CEREAL_CLASS_VERSION macro
|
|
template <class Archive>
|
|
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 <class Archive>
|
|
void save( Archive & ar, AnotherType const & at, std::uint32_t const version )
|
|
{ }
|
|
|
|
template <class Archive>
|
|
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
|
|
```
|
|
|
|
<a name="inheritance"></a>
|
|
|
|
### 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 <iostream>
|
|
#include <cereal/archives/binary.hpp>
|
|
|
|
struct MyBase
|
|
{
|
|
template <class Archive>
|
|
void serialize( Archive & )
|
|
{ }
|
|
};
|
|
|
|
struct MyDerived : MyBase
|
|
{
|
|
template <class Archive>
|
|
void load( Archive & )
|
|
{ }
|
|
|
|
template <class Archive>
|
|
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 <cereal/access.hpp>
|
|
|
|
namespace cereal
|
|
{
|
|
template <class Archive>
|
|
struct specialize<Archive, MyDerived, cereal::specialization::member_load_save> {};
|
|
// 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 `<cereal/specialize.hpp>` [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 `<cereal/macros.hpp>` to change these names. See the [doxygen
|
|
documentation]({{ site.baseurl }}/assets/doxygen/macros_8hpp.html) for more information.
|