How to Serialize Derived Template Classes with Boost.Serialize

Boost serialization of template derived class

You forgot to actually write the NVP to the archive:

ar & boost::serialization::make_nvp("Base1", boost::serialization::base_object<Base<V, int>>(obj) );
ar & boost::serialization::make_nvp("Base2", boost::serialization::base_object<Base<V, std::string>>(obj) );

With that changed, it now prints

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="12">
<a class_id="0" tracking_level="0" version="0">
<Base1 class_id="1" tracking_level="0" version="0">
<obj.u>2.00000000000000000e+00</obj.u>
<obj.v>4</obj.v>
</Base1>
<Base2 class_id="2" tracking_level="0" version="0">
<obj.u>3.00000000000000000e+00</obj.u>
<obj.v>hello</obj.v>
</Base2>
<obj.t>10</obj.t>
</a>

See Live On Coliru

#include <vector>
#include <string>
#include <iostream>
#include <sstream>

#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/serialization/access.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/export.hpp>

template<class U, class V>
struct Base {
Base(U uu, V vv) : u(uu), v(vv) {}
U u;
V v;
};

template<class V, class T>
struct Derived : public Base<V, int>, public Base<V, std::string> {
Derived(T tt) : Base<V, int>(2.0, 4), Base<V, std::string>(3.0, std::string("hello")), t(tt) {}
T t;
};

// does not work
//BOOST_CLASS_EXPORT(Derived);

namespace boost { namespace serialization {

template<class Archive, class U, class V>
void serialize(Archive & ar, Base<U,V> &obj, const unsigned int /*version*/) {
ar& BOOST_SERIALIZATION_NVP(obj.u);
ar& BOOST_SERIALIZATION_NVP(obj.v);
}

template<class Archive, class V, class T>
void serialize(Archive & ar, Derived<V,T> &obj, const unsigned int /*version*/) {
ar & boost::serialization::make_nvp("Base1", boost::serialization::base_object<Base<V, int>>(obj) );
ar & boost::serialization::make_nvp("Base2", boost::serialization::base_object<Base<V, std::string>>(obj) );
// does not work
// ar& BOOST_SERIALIZATION_BASE_OBJECT_NVP(Base<V, int>);
// ar& BOOST_SERIALIZATION_BASE_OBJECT_NVP(Base<V, std::string>);
ar& BOOST_SERIALIZATION_NVP(obj.t);
}

}} // end namespace

int main() {
Derived<double, int> a(10);

std::ostringstream archive_ostream;
boost::archive::xml_oarchive oa(archive_ostream);
oa << BOOST_SERIALIZATION_NVP(a);
std::cout << archive_ostream.str() << std::endl;
}

How to serialize derived template classes with Boost.serialize?

First tell boost that Feature is abstract, is not always needed:

BOOST_SERIALIZATION_ASSUME_ABSTRACT(Feature);

The serialization method should look more or less like this:

template<class Archive> 
void Feature::serialize(Archive & ar, const unsigned int version)
{
ar & BOOST_SERIALIZATION_NVP(some_member);
}

template<typename T,class Archive>
void GenericFeature<T>::serialize(Archive & ar, const unsigned int version)
{
ar & boost::serialization::base_object<Feature>(*this); //serialize base class
ar & BOOST_SERIALIZATION_NVP(some_other_member);
}

Now the tricky point is to register class in serialize/deserialize:

boost::archive::text_iarchive inputArchive(somesstream);

boost::archive::text_oarchive outputArchive(somesstream);

//something to serialize
Feature* one = new GenericFeature<SomeType1>();
Feature* two = new GenericFeature<SomeType2>();
Feature* three = new GenericFeature<SomeType3>();

//register our class, must be all of posible template specyfication
outputArchive.template register_type< GenericFeature<SomeType1> >();
outputArchive.template register_type< GenericFeature<SomeType2> >();
outputArchive.template register_type< GenericFeature<SomeType3> >();

// now simply serialize ;-]
outputArchive << one << two << three;

// register class in deserialization
// must be the same template specification as in serialize
// and in the same correct order or i'm get it wrong ;-D
inputArchive.template register_type< GenericFeature<SomeType1> >();
inputArchive.template register_type< GenericFeature<SomeType2> >();
inputArchive.template register_type< GenericFeature<SomeType3> >();

Feature* another_one;
Feature* another_two;
Feature* another_three;

// and deserialize ;-]
inputArchive >> another_one >> another_two >> another_three;

If you need to hide explicit registering somewhere and make it more automatic, there is idea to make special functor template that register one derived class, create all avaible and put in a single list, that one static method of class Feature would register them all. However the problem will be that you need registration for all version of archive, right now i dont know if polymorphic archive will do the job or not.

Serializing a derived class from an interface

There's two issues at play here:

  1. You are serializing a template class. This is not a problem and yes you can do this intrusively (member serialize) or non-intrusively (free function serialize via ADL). As the documentation (Serializing Templates) states the implementation of shared_ptr<T> serialization is a good example of the non-intrusive variant here:

    • http://www.boost.org/doc/libs/release/boost/serialization/shared_ptr.hpp


  2. You are serializing base/derived classes through a polymorphic pointer. For the serialization part this is nothing special (you can register_type or you should be fine using base_object<Interface>(this) inside the derived's serialize function.

    On the deserialization side of things, however, you need to anticipate the full list of possible concrete instance types serialized through the polymorphic pointer. The BOOST_EXPORT_CLASS macro is the easiest way to achieve this. You will have to list the concrete instances of the template you wish to support, though:

    BOOST_CLASS_EXPORT(Derived<std::string>)
    BOOST_CLASS_EXPORT(Derived<double>)
    BOOST_CLASS_EXPORT(Derived<int>) // include all subtypes we can expect in an input archive

    or

    BOOST_CLASS_EXPORT_GUID(Derived<std::string>, "4ef5a3ff-168a-4242-846b-4886f48424b5")
    BOOST_CLASS_EXPORT_GUID(Derived<double>, "d0ed9de6-584f-476d-9898-8234bcb4efdb")
    BOOST_CLASS_EXPORT_GUID(Derived<int>, "505538f0-2dd1-43bd-92a2-506ed9659bbe") // include all subtypes we can expect in an input archive

The complexity of the situation - and the confusion - arises from the fact that you are serializing a derived class template through a polymorphic pointer. All at the same time. But conceptually both are easily tackled.

Slightly unrelated,

  • Yes, you can use free function serialize, see the 3rd alternative version below. It gains you little though, and just requires m_data to be publicly accessible.
  • Do not use serialize_adl as it is an implementation detail

Here are three samples that integrates everything:

  1. Live On Coliru - raw Interface*

  2. Live On Coliru - same with shared_ptr<Interface>

  3. Live On Coliru - same with non-intrusive serialize function

Listing for the first sample

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <sstream>

class Interface {
public:
virtual void aVirtual() = 0;
virtual ~Interface() {}
private:
friend class boost::serialization::access;
template<class Archive> void serialize(Archive&, unsigned) { }
};

BOOST_SERIALIZATION_ASSUME_ABSTRACT(Interface)

template<class T>
class Derived : public Interface {
public:
Derived(T in = 0) : m_data(in) {}
virtual void aVirtual() { /*Do something*/ }
T const& getData() const { return m_data; }
private:
T m_data;
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, unsigned)
{
ar & boost::serialization::base_object<Interface>(*this);
//// alternatively, if you don't want to make the abstract base serializable:
// boost::serialization::void_cast_register<Derived, Interface>();

ar & m_data;
}
};

BOOST_CLASS_EXPORT(Derived<std::string>)
BOOST_CLASS_EXPORT(Derived<double>)
BOOST_CLASS_EXPORT(Derived<int>) // include all subtypes we can expect in an input archive

int main()
{
std::stringstream ss;

{
boost::archive::text_oarchive oa(ss);

Interface* o = new Derived<int>(42);
oa << o;

delete o;
}

std::cout << "Serialized: '" << ss.str() << "'\n";

{
boost::archive::text_iarchive ia(ss);

Interface* o = nullptr;
ia >> o;

if (auto p = dynamic_cast<Derived<int>*>(o))
std::cout << "Deserialized into Derived<int> with data: " << p->getData() << "\n";

delete o;
}
}

Sample output:

Serialized: '22 serialization::archive 11 0 1 1 12 Derived<int> 1 0
0 42
'
Deserialized into Derived<int> with data: 42

boost::serialization - Serializing a class derived from a generic attribute / feature container

In a nutshell: your serialize member functions should not be virtual. Making them virtual results in the call to static_cast<AttributeContainer*>(this)->serialize(...), made by boost::serialization::base_object inside of Tab::serialize_attributes, landing back in Tab::serialize through virtual function dispatch.

Here's a working single-file example based on your code:

namespace serial_test
{
using namespace std;

class NetworkSerializable {
friend class boost::serialization::access;
public:
typedef std::shared_ptr<NetworkSerializable> NetworkSerializablePtr;

NetworkSerializable() {};

protected:
// void serialize(boost::archive::text_oarchive& oa, const unsigned int version) = 0;
// void serialize(boost::archive::text_iarchive& ia, const unsigned int version) = 0;
};

BOOST_SERIALIZATION_ASSUME_ABSTRACT(NetworkSerializable);

class AttributeBase : public NetworkSerializable {
friend class AttributeContainer;
friend class boost::serialization::access;
public:
AttributeBase() {}
virtual ~AttributeBase() {}

protected:
std::string _name;
std::string _description;

template<class archive>
inline void serialize_attributes(archive& ar, const unsigned int version) {
ar & boost::serialization::make_nvp("Name", _name);
ar & boost::serialization::make_nvp("Description", _description);
}

void serialize(boost::archive::text_oarchive& oa, const unsigned int version) {
cout << "AttributeBase::serialize" << endl;
serialize_attributes(oa, version);
}
void serialize(boost::archive::text_iarchive& ia, const unsigned int version) {
serialize_attributes(ia, version);
}

}; // end class AttributeBase

template <typename _T>
class Attribute : public AttributeBase {
friend class AttributeContainer;
friend class boost::serialization::access;
public:
typedef _T AttributeType;

Attribute() : _data(0) {}
Attribute(const std::string& name, const std::string& description, AttributeType* var) : _data(var) {
_name = name;
_description = description;
}

virtual ~Attribute() {}

protected:
AttributeType* _data;

template <class archive>
void serialize_base(archive& ar, const unsigned int version) {
ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(AttributeBase);
ar & boost::serialization::make_nvp("Value", *_data);
}

void serialize(boost::archive::text_oarchive& oa, const unsigned int version) {
std::cout << "Attribute::serialize" << std::endl;
serialize_base(oa, version);
}

void serialize(boost::archive::text_iarchive& ia, const unsigned int version) {
serialize_base(ia, version);
}
};

class AttributeContainer : public NetworkSerializable {
friend class boost::serialization::access;
public:
std::map<std::string, AttributeBase*> _attributes;

AttributeContainer() {};
virtual ~AttributeContainer() {};

template <typename _T>
void RegisterAttribute(const std::string& name, const std::string& description, _T* var) {
std::map<std::string, AttributeBase*>::const_iterator pos;

if ( (pos = _attributes.find(name)) == _attributes.end() ) {
Attribute<_T>* attribute = new Attribute<_T>(name, description, var);

_attributes.insert(std::map<std::string, AttributeBase*>::value_type(name, attribute));
}
};

template <class archive>
void serialize_attributes(archive& ar, const unsigned int version) {
ar & _attributes;
};

void serialize(boost::archive::text_oarchive& oa, const unsigned int version) {
std::cout << "AttributeContainer::serialize" << std::endl;
serialize_attributes(oa, version);
}
void serialize(boost::archive::text_iarchive& ia, const unsigned int version) {
serialize_attributes(ia, version);
}

}; // end class AtributeContainer

class Tab : public AttributeContainer {
friend class boost::serialization::access;
public:
Tab(const std::string tabName)
: _tabName(tabName) {}
virtual ~Tab() {}

protected:
Tab()
: _tabName("") {}

template<class archive>
inline void serialize_attributes(archive& ar, const unsigned int version) {
// ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(AttributeContainer);
ar & boost::serialization::base_object<AttributeContainer>(*this);
ar & boost::serialization::make_nvp("TabName", _tabName);
}

void serialize(boost::archive::text_oarchive& oa, const unsigned int version) {
std::cout << "Tab::serialize" << std::endl;
serialize_attributes(oa, version);
}

void serialize(boost::archive::text_iarchive& ia, const unsigned int version) {
serialize_attributes(ia, version);
}

private:
std::string _tabName;

};

void test() {
std::ostringstream oarchiveStream;

boost::archive::text_oarchive outputArchive(oarchiveStream);

Tab* tab = new Tab("temp");

bool tempBool = true;
tab->RegisterAttribute("tempBool", "a temp boolean", &tempBool);
std::string tempString("1234");
tab->RegisterAttribute("tempString", "a temp string", &tempString);

outputArchive << tab;
}

} // namespace serial_test

BOOST_SERIALIZATION_ASSUME_ABSTRACT(serial_test::AttributeBase);
BOOST_CLASS_EXPORT_KEY(serial_test::AttributeBase);

BOOST_CLASS_EXPORT_KEY(serial_test::Attribute<bool>);
BOOST_CLASS_EXPORT_KEY(serial_test::Attribute<string>);
BOOST_CLASS_EXPORT_IMPLEMENT(serial_test::Attribute<bool>);
BOOST_CLASS_EXPORT_IMPLEMENT(serial_test::Attribute<string>);

BOOST_CLASS_EXPORT_KEY(serial_test::Tab);
BOOST_CLASS_EXPORT_IMPLEMENT(serial_test::Tab);

boost::serialisation for inherited template class

Well, the problem is quite obvious: <> don't go well inside XML element tags.

So, here's what you I naively thought of to fix it.

ar & boost::serialization::make_nvp(
"base", static_cast<classOne<T>&>(*this));

IMPORTANT UPDATE However, just today, I stumbled across the documentation here:

Note the serialization of the base classes from the derived class. Do NOT directly call the base class serialize functions. Doing so might seem to work but will bypass the code that tracks instances written to storage to eliminate redundancies. It will also bypass the writing of class version information into the archive. For this reason, it is advisable to always make member serialize functions private.

Oops. So here's the new and correct advice:

    typedef classOne<T> baseClass;
ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(baseClass);

Now, you control the name of the xml element by working with the macro and using a typedef to hide the brackets.

Output:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="10">
<ch2 class_id="0" tracking_level="0" version="0">
<baseClass class_id="1" tracking_level="0" version="0">
<st>0.5</st>
</baseClass>
<error>0.5</error>
</ch2>


Related Topics



Leave a reply



Submit