The Boost C++ Libraries

Serialization of Class Hierarchy Objects

Derived classes must access the function boost::serialization::base_object() inside the member function serialize() to serialize objects based on class hierarchies. This function guarantees that inherited member variables of base classes are correctly serialized.

Example 64.11. Serializing derived classes correctly
#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_;
};

class bird : public animal
{
public:
  bird() = default;
  bird(int legs, bool can_fly) :
    animal{legs}, can_fly_{can_fly} {}
  bool can_fly() const { return can_fly_; }

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

  template <typename Archive>
  void serialize(Archive &ar, const unsigned int version)
  {
    ar & boost::serialization::base_object<animal>(*this);
    ar & can_fly_;
  }

  bool can_fly_;
};

void save()
{
  text_oarchive oa{ss};
  bird penguin{2, false};
  oa << penguin;
}

void load()
{
  text_iarchive ia{ss};
  bird penguin;
  ia >> penguin;
  std::cout << penguin.legs() << '\n';
  std::cout << std::boolalpha << penguin.can_fly() << '\n';
}

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

Example 64.11 uses a class called bird, which is derived from animal. Both animal and bird define a private member function serialize() that makes it possible to serialize objects based on either class. Because bird is derived from animal, serialize() must ensure that member variables inherited from animal are serialized, too.

Inherited member variables are serialized by accessing the base class inside the member function serialize() of the derived class and calling boost::serialization::base_object(). You must use this function rather than, for example, static_cast because only boost::serialization::base_object() ensures correct serialization.

Addresses of dynamically created objects can be assigned to pointers of the corresponding base class type. Example 64.12 shows that Boost.Serialization can serialize them correctly as well.

Example 64.12. Registering derived classes statically with BOOST_CLASS_EXPORT
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/export.hpp>
#include <iostream>
#include <sstream>

using namespace boost::archive;

std::stringstream ss;

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

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

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

  int legs_;
};

class bird : public animal
{
public:
  bird() = default;
  bird(int legs, bool can_fly) :
    animal{legs}, can_fly_{can_fly} {}
  bool can_fly() const { return can_fly_; }

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

  template <typename Archive>
  void serialize(Archive &ar, const unsigned int version)
  {
    ar & boost::serialization::base_object<animal>(*this);
    ar & can_fly_;
  }

  bool can_fly_;
};

BOOST_CLASS_EXPORT(bird)

void save()
{
  text_oarchive oa{ss};
  animal *a = new bird{2, false};
  oa << a;
  delete a;
}

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

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

The program creates an object of type bird inside the function save() and assigns it to a pointer of type animal*, which in turn is serialized via operator<<.

As mentioned in the previous section, the referenced object is serialized, not the pointer. To have Boost.Serialization recognize that an object of type bird must be serialized, even though the pointer is of type animal*, the class bird needs to be declared. This is done using the macro BOOST_CLASS_EXPORT, which is defined in boost/serialization/export.hpp. Because the type bird does not appear in the pointer definition, Boost.Serialization cannot serialize an object of type bird correctly without the macro.

The macro BOOST_CLASS_EXPORT must be used if objects of derived classes are to be serialized using a pointer to their corresponding base class. A disadvantage of BOOST_CLASS_EXPORT is that, because of static registration, classes can be registered that may not be used for serialization at all. Boost.Serialization offers a solution for this scenario.

Example 64.13. Registering derived classes dynamically with register_type()
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/export.hpp>
#include <iostream>
#include <sstream>

std::stringstream ss;

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

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

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

  int legs_;
};

class bird : public animal
{
public:
  bird() = default;
  bird(int legs, bool can_fly) :
    animal{legs}, can_fly_{can_fly} {}
  bool can_fly() const { return can_fly_; }

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

  template <typename Archive>
  void serialize(Archive &ar, const unsigned int version)
  {
    ar & boost::serialization::base_object<animal>(*this);
    ar & can_fly_;
  }

  bool can_fly_;
};

void save()
{
  boost::archive::text_oarchive oa{ss};
  oa.register_type<bird>();
  animal *a = new bird{2, false};
  oa << a;
  delete a;
}

void load()
{
  boost::archive::text_iarchive ia{ss};
  ia.register_type<bird>();
  animal *a;
  ia >> a;
  std::cout << a->legs() << '\n';
  delete a;
}

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

Instead of using the macro BOOST_CLASS_EXPORT, Example 64.13 calls the member function template register_type(). The type to be registered is passed as a template parameter. Note that register_type() must be called both in save() and load().

The advantage of register_type() is that only classes used for serialization must be registered. For example, when developing a library, one does not know which classes a developer may use for serialization later. While the macro BOOST_CLASS_EXPORT makes this easy, it may register types that are not going to be used for serialization.