There's many things (besides the boost_1_69_0 thing that we've discussed earlier .
First things first, you define specializations in Foo.cpp
:
template <> void serialize(boost::archive::xml_oarchive& ar, unsigned int const file_version) {}
template <> void serialize(boost::archive::xml_iarchive& ar, unsigned int const file_version) {}
But no such template exists. There's only the member function template
serialize
. Besides, you can instantiate whatever you want, but unless it
is extern
-declared in the header the compiler will refuse to
instantiate the template without a definition.
The same thing (missing extern declaration) will happen with load/save
construct data functions.
You opted to put the load/save construct data inside the
boost::serialization
namespace. This is odd, since apparently there's a
wish to hide implementation detail from the header. I'd define the
functions at their least intrusive and least tightly-coupled spot: within
the class namespace.
Similar things are at play with BOOST_CLASS_EXPORT(Foo)
- since this in a
header, you will be defining stuff in multiple translation units.
Avoid it by splitting declaration and definition (or, you know, opt to not do this rain-dance)
Top-level const
is (literally) meaningless in function signatures
Why are you including the entire archives in the header file? I'd say
having those includes is 100x more impact than including the definitions of
the template functions that you're trying to hide...
Same for iostream
You're exporting a non-polymorphic class. That won't compile. You need a
virtual interface.
You're not serializing through a pointer; polymorphic types don't make
sense, and neither do load/save construct data
Running with these, I'd arrive at the following "more ideal" header:
// Foo.hpp
#include <boost/serialization/export.hpp>
// forward declarations
namespace boost { namespace archive { class xml_iarchive; } }
namespace boost { namespace archive { class xml_oarchive; } }
namespace boost { namespace serialization { class access; } }
struct IFoo {
virtual ~IFoo() = default;
};
class Foo : public IFoo {
int m_bar;
friend class boost::serialization::access;
using iarchive = boost::archive::xml_iarchive;
using oarchive = boost::archive::xml_oarchive;
friend void serialize(iarchive&, Foo&, unsigned);
friend void serialize(oarchive&, Foo&, unsigned);
friend void load_construct_data(iarchive& ar, Foo*, unsigned);
friend void save_construct_data(oarchive& ar, Foo const*, unsigned);
public:
Foo(int bar);
int getBar() const;
};
BOOST_CLASS_EXPORT_KEY(Foo)
The corresponding implementation:
// Foo.cpp
#include "Foo.hpp"
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
Foo::Foo(int bar) : m_bar(bar){};
int Foo::getBar() const { return m_bar; }
void serialize(Foo::iarchive&, Foo&, unsigned) {}
void serialize(Foo::oarchive&, Foo&, unsigned) {}
void save_construct_data(Foo::oarchive& ar, Foo const* p, unsigned) {
int bar = p->getBar();
ar << BOOST_NVP(bar);
}
void load_construct_data(Foo::iarchive& ar, Foo* p, unsigned) {
int bar;
ar >> BOOST_NVP(bar);
::new (p) Foo(bar);
}
BOOST_CLASS_EXPORT_IMPLEMENT(Foo)
Now, fixing main
a little:
#include "Foo.hpp"
#include <boost/archive/xml_oarchive.hpp>
#include <boost/serialization/unique_ptr.hpp>
#include <iostream>
int main() {
auto foo = std::make_unique<Foo>(42);
boost::archive::xml_oarchive oa(std::cout);
oa << BOOST_NVP(foo);
}
Prints:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="19">
<foo class_id="0" tracking_level="0" version="0">
<tx class_id="1" tracking_level="1" version="0" object_id="_0">
<bar>42</bar>
</tx>
</foo>
</boost_serialization>
See it Live On Wandbox
Bonus
If you're actually hiding implementation, why not hide it fully? That's easier
still, can be virtualized etc. It hurts my sensibilities that you're going to
these lengths to "hide" details but still including the archive type inside the
main program... and allowing the user to mismatch serialization/deserialization
e.g. by using a different XML name, or different variable type altogether (which
leads to undefined behaviour).
Let's make the ideal header:
#include <iosfwd>
#include <memory>
// Foo.hpp
struct IFoo {
virtual ~IFoo() = default;
void save(std::ostream&) const;
static std::unique_ptr<IFoo> load(std::istream&);
};
class Foo : public IFoo {
int m_bar;
public:
Foo(int bar);
int getBar() const;
};
I hope you'll agree that this is cutting dependencies and reducing coupling.
The rest was lip-service only.
Now everything truly serialization related can be in the cpp. In a real project you'd have a TU dedicated to serializing an entire object graph. (Simplest thing to do would be to put it in a IFoo.cpp
).
Let's throw in base_object
serialization (that was previously missing):
// Foo.cpp
#include "Foo.hpp"
Foo::Foo(int bar) : m_bar(bar){}
int Foo::getBar() const { return m_bar; }
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/nvp.hpp>
using boost::make_nvp;
using boost::serialization::base_object;
template <typename Archive> void serialize(Archive&, IFoo&, unsigned) {}
template <typename Archive> void serialize(Archive& ar, Foo& f, unsigned) {
ar & boost::make_nvp("IFoo", base_object<IFoo>(f));
}
template <typename Archive> void save_construct_data(Archive& ar, Foo const* p, unsigned) {
int bar = p->getBar();
ar << BOOST_NVP(bar);
}
template <typename Archive> void load_construct_data(Archive& ar, Foo* p, unsigned) {
int bar;
ar >> BOOST_NVP(bar);
::new (p) Foo(bar);
}
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/serialization/export.hpp>
BOOST_CLASS_EXPORT(IFoo)
BOOST_CLASS_EXPORT(Foo)
/*static*/ std::unique_ptr<IFoo> IFoo::load(std::istream& is) {
boost::archive::xml_iarchive ia(is);
IFoo* p = nullptr;
ia >> boost::make_nvp("p", p);
return std::unique_ptr<IFoo>(p);
}
void IFoo::save(std::ostream& os) const {
boost::archive::xml_oarchive oa(os);
IFoo const* p = this;
oa << make_nvp("p", p);
}
Extending main
to also show round-trip deserialization:
#include "Foo.hpp"
#include <iostream>
#include <sstream>
int main() {
Foo(42).save(std::cout);
std::stringstream ss;
Foo(99).save(ss);
auto roundtrip = Foo::load(ss);
if (auto foo = dynamic_cast<Foo*>(roundtrip.get())) {
std::cout << "roundtrip is a foo, getBar(): " << foo->getBar() << "\n";
}
}
Prints
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="19">
<p class_id="1" class_name="Foo" tracking_level="1" version="0" object_id="_0">
<bar>42</bar>
<IFoo class_id="0" tracking_level="1" version="0" object_id="_1"></IFoo>
</p>
</boost_serialization>
roundtrip is a foo, getBar(): 99
See it Live On Wandbox again.