The Boost C++ Libraries

Archive

The main concept of Boost.Serialization is the archive. An archive is a sequence of bytes that represent serialized C++ objects. Objects can be added to an archive to serialize them and then later loaded from the archive. In order to restore previously saved C++ objects, the same types are presumed.

Example 64.1. Using boost::archive::text_oarchive
#include <boost/archive/text_oarchive.hpp>
#include <iostream>

using namespace boost::archive;

int main()
{
  text_oarchive oa{std::cout};
  int i = 1;
  oa << i;
}

Boost.Serialization provides archive classes such as boost::archive::text_oarchive, which is defined in boost/archive/text_oarchive.hpp. This class makes it possible to serialize objects as a text stream. With Boost 1.56.0, Example 64.1 writes 22 serialization::archive 11 1 to the standard output stream.

As can be seen, the object oa of type boost::archive::text_oarchive can be used like a stream to serialize a variable using operator<<. However, archives should not be considered as regular streams that store arbitrary data. To restore data, you must access it as you stored it, using the same data types in the same order. Example 64.2 serializes and restores a variable of type int.

Example 64.2. Using boost::archive::text_iarchive
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <fstream>

using namespace boost::archive;

void save()
{
  std::ofstream file{"archive.txt"};
  text_oarchive oa{file};
  int i = 1;
  oa << i;
}

void load()
{
  std::ifstream file{"archive.txt"};
  text_iarchive ia{file};
  int i = 0;
  ia >> i;
  std::cout << i << '\n';
}

int main()
{
  save();
  load();
}

The class boost::archive::text_oarchive serializes data as a text stream, and the class boost::archive::text_iarchive restores data from such a text stream. To use these classes, include the header files boost/archive/text_iarchive.hpp and boost/archive/text_oarchive.hpp.

Constructors of archives expect an input or output stream as a parameter. The stream is used to serialize or restore data. While Example 64.2 accesses a file, other streams, such as a stringstream, can also be used.

Example 64.3. Serializing with a stringstream
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <sstream>

using namespace boost::archive;

std::stringstream ss;

void save()
{
  text_oarchive oa{ss};
  int i = 1;
  oa << i;
}

void load()
{
  text_iarchive ia{ss};
  int i = 0;
  ia >> i;
  std::cout << i << '\n';
}

int main()
{
  save();
  load();
}

Example 64.3 writes 1 to standard output using a stringstream to serialize data.

So far, only primitive types have been serialized. Example 64.4 shows how to serialize objects of user-defined types.

Example 64.4. Serializing user-defined types with a member function
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <sstream>

using namespace boost::archive;

std::stringstream ss;

class animal
{
public:
  animal() = default;
  animal(int legs) : legs_{legs} {}
  int legs() const { return legs_; }

private:
  friend class boost::serialization::access;

  template <typename Archive>
  void serialize(Archive &ar, const unsigned int version) { ar & legs_; }

  int legs_;
};

void save()
{
  text_oarchive oa{ss};
  animal a{4};
  oa << a;
}

void load()
{
  text_iarchive ia{ss};
  animal a;
  ia >> a;
  std::cout << a.legs() << '\n';
}

int main()
{
  save();
  load();
}

In order to serialize objects of user-defined types, you must define the member function serialize(). This function is called when the object is serialized to or restored from a byte stream. Because serialize() is used for both serializing and restoring, Boost.Serialization supports the operator operator& in addition to operator<< and operator>>. With operator& there is no need to distinguish between serializing and restoring within serialize().

serialize() is automatically called any time an object is serialized or restored. It should never be called explicitly and, thus, should be declared as private. If it is declared as private, the class boost::serialization::access must be declared as a friend to allow Boost.Serialization to access the member function.

There may be situations that do not you allow to modify an existing class in order to add serialize(). For example, this is true for classes from the standard library.

Example 64.5. Serializing with a free-standing function
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <sstream>

using namespace boost::archive;

std::stringstream ss;

struct animal
{
  int legs_;

  animal() = default;
  animal(int legs) : legs_{legs} {}
  int legs() const { return legs_; }
};

template <typename Archive>
void serialize(Archive &ar, animal &a, const unsigned int version)
{
  ar & a.legs_;
}

void save()
{
  text_oarchive oa{ss};
  animal a{4};
  oa << a;
}

void load()
{
  text_iarchive ia{ss};
  animal a;
  ia >> a;
  std::cout << a.legs() << '\n';
}

int main()
{
  save();
  load();
}

In order to serialize types that cannot be modified, the free-standing function serialize() can be defined as shown in Example 64.5. This function expects a reference to an object of the corresponding type as its second parameter.

Implementing serialize() as a free-standing function requires that essential member variables of a class can be accessed from outside. In Example 64.5, serialize() can only be implemented as a free-standing function since legs_ is no longer a private member variable of the class animal.

Boost.Serialization provides serialize() functions for many classes from the standard library. To serialize objects based on standard classes, additional header files need to be included.

Example 64.6. Serializing strings
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/string.hpp>
#include <iostream>
#include <sstream>
#include <string>
#include <utility>

using namespace boost::archive;

std::stringstream ss;

class animal
{
public:
  animal() = default;
  animal(int legs, std::string name) :
    legs_{legs}, name_{std::move(name)} {}
  int legs() const { return legs_; }
  const std::string &name() const { return name_; }

private:
  friend class boost::serialization::access;

  template <typename Archive>
  friend void serialize(Archive &ar, animal &a, const unsigned int version);

  int legs_;
  std::string name_;
};

template <typename Archive>
void serialize(Archive &ar, animal &a, const unsigned int version)
{
  ar & a.legs_;
  ar & a.name_;
}

void save()
{
  text_oarchive oa{ss};
  animal a{4, "cat"};
  oa << a;
}

void load()
{
  text_iarchive ia{ss};
  animal a;
  ia >> a;
  std::cout << a.legs() << '\n';
  std::cout << a.name() << '\n';
}

int main()
{
  save();
  load();
}

Example 64.6 extends the class animal by adding name_, a member variable of type std::string. In order to serialize this member variable, the header file boost/serialization/string.hpp must be included to provide the appropriate free-standing function serialize().

As mentioned before, Boost.Serialization defines serialize() functions for many classes from the standard library. These functions are defined in header files that carry the same name as the corresponding header files from the standard. So, to serialize objects of type std::string, include the header file boost/serialization/string.hpp and to serialize objects of type std::vector, include the header file boost/serialization/vector.hpp. It is fairly obvious which header file to include.

One parameter of serialize() that has been ignored so far is version. This parameter helps make archives backward compatible. Example 64.7 can load an archive that was created by Example 64.5. The version of the class animal in Example 64.5 did not contain a name. Example 64.7 checks the version number when loading an archive and only accesses the name if the version is greater than 0. This allows it to handle an older archive that was created without name.

Example 64.7. Backward compatibility with version numbers
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/string.hpp>
#include <iostream>
#include <sstream>
#include <string>
#include <utility>

using namespace boost::archive;

std::stringstream ss;

class animal
{
public:
  animal() = default;
  animal(int legs, std::string name) :
    legs_{legs}, name_{std::move(name)} {}
  int legs() const { return legs_; }
  const std::string &name() const { return name_; }

private:
  friend class boost::serialization::access;

  template <typename Archive>
  friend void serialize(Archive &ar, animal &a, const unsigned int version);

  int legs_;
  std::string name_;
};

template <typename Archive>
void serialize(Archive &ar, animal &a, const unsigned int version)
{
  ar & a.legs_;
  if (version > 0)
    ar & a.name_;
}

BOOST_CLASS_VERSION(animal, 1)

void save()
{
  text_oarchive oa{ss};
  animal a{4, "cat"};
  oa << a;
}

void load()
{
  text_iarchive ia{ss};
  animal a;
  ia >> a;
  std::cout << a.legs() << '\n';
  std::cout << a.name() << '\n';
}

int main()
{
  save();
  load();
}

The macro BOOST_CLASS_VERSION assigns a version number to a class. The version number for the class animal in Example 64.7 is 1. If BOOST_CLASS_VERSION is not used, the version number is 0 by default.

The version number is stored in the archive and is part of it. While the version number specified for a particular class via the BOOST_CLASS_VERSION macro is used during serialization, the parameter version of serialize() is set to the value stored in the archive when restoring. If the new version of animal accesses an archive containing an object serialized with the old version, the member variable name_ would not be restored because the old version did not have such a member variable.

Exercise

Create a program which serializes an object of type std::runtime_error to a file and loads it again.