Die Boost C++ Bibliotheken

Serialisieren von Objekten aus Klassenhierarchien

Wenn Objekte serialisiert werden sollen, die auf Typen aus Klassenhierarchien basieren, muss in Kindklassen innerhalb der Methode serialize() auf eine Funktion boost::serialization::base_object() zugegriffen werden. Nur diese Funktion stellt sicher, dass von Elternklassen geerbte Eigenschaften einwandfrei serialisiert werden. Sehen Sie sich dazu Beispiel 64.11 an, das um eine Klasse bird erweitert wurde, die von animal abgeleitet ist.

Beispiel 64.11. Kindklassen richtig serialisieren
#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();
}

Beide Klassen – sowohl animal als auch bird – besitzen eine private Methode serialize(). Demnach können Objekte, die auf einer dieser Klassen basieren, serialisiert werden. Weil bird jedoch von animal abgeleitet ist, muss die Methode serialize() in dieser Klasse dafür sorgen, dass die von animal geerbten Eigenschaften ebenfalls serialisiert werden.

Geerbte Eigenschaften werden serialisiert, indem in der Kindklasse in serialize() mit boost::serialization::base_object() auf die Elternklasse zugegriffen wird. Es ist zwingend notwendig, diese Funktion und nicht zum Beispiel static_cast zu verwenden, weil nur boost::serialization::base_object() eine einwandfreie Serialisierung sicherstellt.

Wenn Objekte dynamisch erzeugt werden, können ihre Adressen in Zeigern vom Typ der Elternklasse gespeichert werden. Dass Boost.Serialization auch in diesem Fall Objekte korrekt serialisieren kann, zeigt Beispiel 64.12.

Beispiel 64.12. Kindklassen statisch mit BOOST_CLASS_EXPORT registrieren
#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();
}

Im Beispiel 64.12 wird in der Funktion save() ein Objekt vom Typ bird erstellt und in einem Zeiger vom Typ animal* verankert. Dieser Zeiger wird per operator<< an das Archiv übergeben.

Wie bereits im vorherigen Abschnitt gesehen, wird in diesem Fall automatisch das Objekt serialisiert, auf das der Zeiger verweist. Damit Boost.Serialization erkennt, dass ein Objekt vom Typ bird serialisiert werden muss, obwohl der Zeiger den Typ animal* hat, muss die Klasse bird der Bibliothek bekannt gemacht werden. Dies erfolgt mit Hilfe des Makros BOOST_CLASS_EXPORT, das in der Headerdatei boost/serialization/export.hpp definiert ist. Ohne dieses Makro könnte Boost.Serialization das Objekt vom Typ bird nicht richtig serialisieren, weil der Typ bird nicht in der Zeigerdefinition auftaucht und daher für Boost.Serialization unbekannt wäre.

Sie müssen das Makro BOOST_CLASS_EXPORT immer dann verwenden, wenn Sie Kindklassen erstellen und Objekte vom Typ dieser Kindklassen serialisieren wollen, dies aber über einen Zeiger vom Typ einer Elternklasse tun.

Ein Nachteil des Makros BOOST_CLASS_EXPORT ist, dass aufgrund der statischen Registrierung auch Klassen registiert werden, die womöglich in einem Programm nicht zur Serialisierung verwendet werden. Boost.Serialization bietet auch für diesen Fall eine Lösung an.

Beispiel 64.13. Kindklassen dynamisch mit register_type() registrieren
#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();
}

Anstatt das Makro BOOST_CLASS_EXPORT zu verwenden, wird im Beispiel 64.13 für ein Archiv die Methode register_type() aufgerufen. Ihr wird der entsprechende Typ, der registriert werden soll, als Template-Parameter übergeben.

Beachten Sie, dass register_type() sowohl in save() als auch in load() aufgerufen werden muss.

Der Vorteil der dynamischen Registrierung mit register_type() ist, dass zur Laufzeit entschieden werden kann, welche Typen für die Serialisierung verwendet und daher registriert werden müssen.