user6386155
user6386155

Reputation: 857

Boost serialization for multiple inheritance

Consider

class B;
class C;
class A: public  B, public  C
{
 int a;
 ...
}

Is this the correct way of serialization?

friend class  boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int ver)
{
    ar & boost::serialization::base_object<B>(*this);
    ar & boost::serialization::base_object<C>(*this);
    ar & a;
    ...
}

Does the order matter(for serialization and deserialization)?

Upvotes: 2

Views: 988

Answers (1)

sehe
sehe

Reputation: 392833

What's best depends a lot on what you intend to do. In many cases the fact that the class is composed by inheritance is not a relevant implementation detail for serialization, so the "best" implementation could easily look like

template<class Archive>
void serialize(Archive & ar, unsigned) {
    ar & a;
}

However, assuming you have references to base-classes elsewhere and you do require tracking on these, then you should probbaly use what you had, with some modifications in case you intend to use XML archives:

Live On Coliru

#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/string.hpp>

class B {
    std::string name = "Thor";
    friend class boost::serialization::access;
    template <class Archive> void serialize(Archive &ar, unsigned) { ar & BOOST_SERIALIZATION_NVP(name); }
};
class C {
    double trouble;
    friend class boost::serialization::access;
    template <class Archive> void serialize(Archive &ar, unsigned) { ar & BOOST_SERIALIZATION_NVP(trouble); }
};

class A : public B, public C {
    int a = 42;

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

    template <class Archive> void serialize(Archive &ar, unsigned) {

        ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(B)
           & BOOST_SERIALIZATION_BASE_OBJECT_NVP(C)
           & BOOST_SERIALIZATION_NVP(a);
    }
};

struct Test {
    A derived;
    B* baseb = nullptr;
    C* basec = nullptr;
  private:
    friend class boost::serialization::access;
    template <class Archive> void serialize(Archive &ar, unsigned) {
        ar & BOOST_SERIALIZATION_NVP(derived)
           & BOOST_SERIALIZATION_NVP(baseb)
           & BOOST_SERIALIZATION_NVP(basec);
    }
};

#include <iostream>
#include <fstream>
int main(int argc, char**) {
    if (argc == 1) {
        std::ofstream ofs("test.xml");
        boost::archive::xml_oarchive oa(ofs);

        Test test;
        test.baseb = &test.derived; // for asserting below
        test.basec = &test.derived; // for asserting below
        oa << BOOST_SERIALIZATION_NVP(test);
    }

    std::cout << std::ifstream("test.xml").rdbuf();

    {
        std::ifstream ifs("test.xml");
        boost::archive::xml_iarchive ia(ifs);

        Test test;
        ia >> BOOST_SERIALIZATION_NVP(test);

        assert(static_cast<B*>(&test.derived) == test.baseb);
        assert(static_cast<C*>(&test.derived) == test.basec);
    }
}

The order shouldn't matter (technically the construction step is separate and happens before the serialize function runs). However, I'd personally put the base serialization in order of declaration.

Output from the sample:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="16">
<test class_id="0" tracking_level="0" version="0">
    <derived class_id="1" tracking_level="0" version="0">
        <B class_id="2" tracking_level="1" version="0" object_id="_0">
            <name>Thor</name>
        </B>
        <C class_id="3" tracking_level="1" version="0" object_id="_1">
            <trouble>1.68426978667280947e-320</trouble>
        </C>
        <a>42</a>
    </derived>
    <baseb class_id_reference="2" object_id_reference="_0"></baseb>
    <basec class_id_reference="3" object_id_reference="_1"></basec>
</test>
</boost_serialization>

Note from the docs:

Resist the temptation to just cast *this to the base class. This might seem to work but may fail to invoke code necessary for proper serialization.

Note that this is NOT the same as calling the serialize function of the base class. This might seem to work but will circumvent certain code used for tracking of objects, and registering base-derived relationships and other bookkeeping that is required for the serialization system to function as designed.

For this reason, all serialize member functions should be private.

Upvotes: 1

Related Questions