Transitioning From Boost

If you are coming from the world of Boost serialization, you will find that cereal supports almost every feature you are used to using. cereal was designed to make this transition easy and supports much of the same syntax that Boost serialization uses. This is a short guide on making the transition. Make sure you’ve properly set up cereal and gotten familiar with its basic syntax by checkout out the quickstart first.


TLDR Version

cereal has a very similar interface to Boost serialization and in some cases can immediately be dropped in by simply replacing the Boost archives with cereal archives. cereal does some things in fairly different ways, so consult the rest of this documentation or the doxygen documentation for more details.


Key Differences From Boost

  • cereal attempts to store as little metadata as possible when saving data. Boost, by default, stores various metadata about the library version as well as the types themselves. cereal will not perform any checks to ensure that serialization was done with the same version of cereal.

  • cereal is header only, meaning that there is nothing to link against. One of the most frustrating issues when using Boost serialization is keeping Boost versions consistent across different machines - cereal can easily be installed standalone or embedded in your project to keep versions consistent.

  • cereal supports almost everything in the standard library out of the box. Some of the things cereal supports by default that Boost does not include: <forward_list>, <memory>, <queue>, <stack>, <tuple>, <unordered_set>, and <unordered_map>.

  • cereal cannot handle raw pointers and requires a fairly complete c++11 implementation to run. From a development standpoint, the code of cereal should be significantly easier to comprehend and extend.

  • cereal can be less verbose than Boost, e.g. automatically inferring the use of a split load/save pair instead of a single serialize function without the need to use a macro. cereal uses static_assert to give meaningful errors when you make mistakes.

  • cereal has a different preferred syntax for serialization. While Boost uses &, <<, and >> to send things to archives, the preferred style in cereal is to use the () operator upon an archive, e.g. archive( myData1, myData2 ). To ease the transition, cereal also supports all of the aforementioned Boost syntax. Many other features of cereal bear strong resemblence to Boost but may have different names or operate in a slightly different manner - it is suggested to carefully read the doxygen before using any foreign cereal features.


An Example Transition

To start using cereal, you may not even need to change any of your serialization code. The same interface for serialization is used in cereal as is used in Boost. Consider this example with the minimal changes necessary to support using cereal:

#include <boost/archive/binary_oarchive.hpp>
#include <cereal/archives/binary.hpp>
#include <fstream>

class SomeData
{
  public:
    SomeData() = default;
    int a;
    int b;

  private:
    friend class cereal::access; // befriend the cereal version of access
    friend class boost::serialization::access;

    // cereal supports class versioning although it is considered
    // optional in cereal. 
    //
    // Note that the second parameter is changed from const unsigned int to 
    // const std::uint32_t
    template <class Archive>
    void save( Archive & ar, std::uint32_t const version ) const
    {
      // cereal supports Boost style syntax until you can change it
      ar << a << b;
    }

    template <class Archive>
    void load( Archive & ar, std::uint32_t const version )
    {
      ar >> a;
      ar >> b;
    }

  // cereal will figure out you've used split member load/save automatically
  BOOST_SERIALIZATION_SPLIT_MEMBER()
};

// Note that the Boost macro BOOST_SERIALIZATION_SPLIT_MEMBER
// creates a function named serialize, which creates a conflict for 
// cereal, which does not allow more than one type of serialization
// function to be present.
//
// To get around this, you should temporarily use specialization (see access.hpp)
// to prevent cereal from seeing this as an error.  Once you remove the Boost
// macro, you may remove this specialization as well
CEREAL_SPECIALIZE_FOR_ALL_ARCHIVES( SomeData, cereal::specialization::member_load_save )

BOOST_CLASS_VERSION(SomeData, 1);
CEREAL_CLASS_VERSION(SomeData, 1); // many things have the same or similar names in cereal

struct MyType
{
  int x;
  double y;
  SomeData s;

  template <class Archive>
  void serialize( Archive & ar, std::uint32_t const version )
  {
    ar & x & y; // the & operator is valid in cereal but not the preferred interface
    ar & s;
  }
};

int main()
{
  std::ofstream os("out.bin", std::ios::binary);

  // using boost
  {
    boost::archive::binary_oarchive ar(os);
    MyType m;
    ar << m; // cereal supports Boost style serialization syntax until you can fully transition
  }

  // using cereal
  {
    cereal::BinaryOutputArchive ar(os);
    MyType m;
    ar << m;
  }
  
  return 0;
}

The only necessary change to the above code was using a cereal archive instead of a Boost archive and adding the cereal named friend and class versioning. If you are using other Boost features, there will be more changes to make to your code.

In Boost, if you use a split load/save pairing, you must use the BOOST_MEMBER_SPLIT macro, whereas cereal has no equivalent - simply create your split load/save pair.

If you befriend boost::serialization::access, that must change to cereal::access, defined in <cereal/access.hpp>.

In cereal, class versioning is an exception and not the rule, so most serialization functions will ommit the std::uint32_t const secondary parameter. However, keeping this parameter is completely supported by cereal and detailed elsewhere. The macro BOOST_CLASS_VERSION has an equivalent called CEREAL_CLASS_VERSION. Many other features have very similar names or similar functionality, but will not always be the same. It is highly recommended you consult the detailed doxygen documentation on any feature that is unknown to you.

Below is the above code re-written in the preferred cereal style:

#include <cereal/archives/binary.hpp>
#include <fstream>

class SomeData
{
  public:
    SomeData() = default;
    int a;
    int b;

  private:
    friend class cereal::access;

    // cereal supports class versioning although it is considered
    // optional in cereal
    template <class Archive>
    void save( Archive & ar, std::uint32_t const version ) const
    {
      ar( a, b ); // operator() is the preferred way of interfacing the archive
    }

    template <class Archive>
    void load( Archive & ar, std::uint32_t const version )
    {
      ar( a, b );
    }

    // note the lack of explicitly informing cereal to use a split member load/save
};

CEREAL_CLASS_VERSION(SomeData, 1);

struct MyType
{
  int x;
  double y;
  SomeData s;

  template <class Archive>
  void serialize( Archive & ar, std::uint32_t const version )
  {
    ar( x, y );
    ar( s );
  }
};

int main()
{
  std::ofstream os("out.bin", std::ios::binary);

  {
    cereal::BinaryOutputArchive ar(os);
    MyType m;
    ar( m );
  }
  
  return 0;
}