Is there a way to serialize iterators using boost serialization? - c++

In my project I have a class containing and std::list and in another class I maintain an iterator pointing to a place in the middle of that list.
I can successfully serialize the list, but the iterator member variable is causing problems. Here is a program to reproduce:
#include <boost/serialization/list.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <fstream>
class A
{
public:
A(){}
A(const std::list<int> & _i) : i(_i) {}
virtual ~A(){}
std::list<int> i;
void display() {
std::cout << "i:";
for (const int j : i)
std::cout << " " << j;
std::cout << std::endl;
}
private:
friend class boost::serialization::access;
//friend std::ostream & operator<<(std::ostream &os, const A &a);
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & i;
}
};
class Stepper
{
public:
Stepper() {}
Stepper(const A& a)
: p(a.i.size()>0 ? a.i.begin() : a.i.end()) {}
std::list<int>::const_iterator p;
void display() {
std::cout << "p: " << *p << std::endl;
}
void step() { p++; }
private:
friend class boost::serialization::access;
//friend std::ostream & operator<<(std::ostream &os, const A &a);
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & p;
}
};
int main()
{
{
A a({5,6,7});
Stepper sa(a);
a.display();
sa.display();
sa.step();
sa.display();
std::ofstream ofs( "a.txt" );
boost::archive::text_oarchive ar(ofs);
ar & a;
ar & sa;
}
A b;
Stepper sb;
{
std::ifstream ifs( "a.txt" );
boost::archive::text_iarchive ar(ifs);
ar & b;
ar & sb;
}
b.display();
sb.display();
return 0;
}
In this program, the class A can be serialized without problems. (Remove the ar&sa stuff..) But unfortunately when trying to serialize the class containing the iterator (the exact code above), I get the following compilation errors:
[..snip..]
testser.cpp:72:10: required from here /usr/include/boost/serialization/access.hpp:116:11:
error: ‘struct std::_List_const_iterator<int>’ has no member named ‘serialize’
t.serialize(ar, file_version);
~~^~~~~~~~~
[..snip..]
testser.cpp:81:10: required from here /usr/include/boost/serialization/access.hpp:116:11:
error: ‘struct std::_List_const_iterator<int>’ has no member named ‘serialize’
So, it seems that boost/serialization/list.hpp does not support iterators. And yet, as far as I can tell, it's totally legitimate to keep an iterator to a list item somewhere, as they cannot be invalidated unless erased. Is there a way to serialize this iterator using boost? Do I need to write a custom function? Do I have to return a custom iterator from my std::list? (That sounds particularly ugly..)
Thanks for any insight.

Okay it seems the only way to do this is to split the serialization into save and load, and calculate the iterator's position in the list. This works as long as the iterator is valid. Unfortunately it means needing to add a pointer to the list to the structure, which I didn't want, but actually in my application I can access this so it is not a problem for me.
class Stepper
{
public:
Stepper() {}
Stepper(const A& _a)
: a(&_a), p(a->i.size()>0 ? a->i.begin() : a->i.end()) {}
const A* a;
std::list<int>::const_iterator p;
void display() {
std::cout << "p: " << *p << std::endl;
}
void step() { p++; }
private:
friend class boost::serialization::access;
template<class Archive>
void save(Archive &ar, const unsigned int version) const
{
int d = std::distance(a->i.begin(), p);
ar & a;
ar & d;
}
template<class Archive>
void load(Archive &ar, const unsigned int version)
{
int d;
ar & a;
ar & d;
p = a->i.begin();
for (; d>0; --d)
p++;
}
BOOST_SERIALIZATION_SPLIT_MEMBER()
};

Related

Derived class serialization - 'unregistered class' exception

Trying to get boost serialization to work. The main idea is to have a base class that contains a few members and some derived classes, each derived class has it's own private members alongside with the base class members.
This is a minimal version of what I'm trying to do:
class base {
public:
virtual void print() = 0;
private:
friend class boost::serialization::access;
template<class Archive> void serialize(Archive &ar, const unsigned) {}
}
class derived20 : public base {
private:
friend class boost::serialization::access;
template<class Archive> void serialize(Archive &ar, const unsigned) {
boost::serialization::base_object<base>(*this);
ar & var20;
}
int var20 = 20;
public:
void print() override { std::cout << var20 << std::endl; }
}
class derived40 : public base {
private:
friend class boost::serialization::access;
template<class Archive> void serialize(Archive &ar, const unsigned) {
boost::serialization::base_object<base>(*this);
ar & var40;
}
int var40 = 40;
public:
void print() override { std::cout << var40 << std::endl; }
}
BOOST_CLASS_EXPORT(derived20)
BOOST_CLASS_EXPORT(derived40)
BOOST_CLASS_EXPORT(base)
int main(int argc, char **argv) {
std::stringstream ss;
boost::archive::text_oarchive ar(ss);
auto d20 = new derived20();
ar << d20;
// prints:
// 22 serialization:archive 15 0 1 0
// 0 20
std::cout << ss.str() << std::endl;
boost::archive::text_iarchive iar(ss);
base *b;
iar >> b;
b->print(); // Should print "20"
}
I'm getting an archive_exception: unregistered class error.
What on earth am I doing wrong?
This is one rare occasion where auto might hurt:
auto d20 = new derived20();
ar << d20;
Contrast with:
base *b;
iar >> b;
Note how d20 is derived20*, not base*. So you're invoking Undefined Behaviour by attempting to deserialize a type from an archive that contains another type.
Simply changing that:
base* d20 = new derived20();
Fixes everything:
Also noticed that you forgot to actually serialize the base object.
Live on coliru
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/access.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
class base {
public:
virtual void print() = 0;
private:
friend class boost::serialization::access;
template<class Archive> void serialize(Archive & /*unused*/, unsigned /*unused*/) {}
};
class derived20 : public base {
private:
friend class boost::serialization::access;
template<class Archive> void serialize(Archive &ar, unsigned /*unused*/) {
ar & boost::serialization::base_object<base>(*this)
& var20;
}
int var20 = 20;
public:
void print() override { std::cout << var20 << std::endl; }
};
class derived40 : public base {
private:
friend class boost::serialization::access;
template<class Archive> void serialize(Archive &ar, const unsigned /*unused*/) {
ar & boost::serialization::base_object<base>(*this)
& var40;
}
int var40 = 40;
public:
void print() override { std::cout << var40 << std::endl; }
};
BOOST_CLASS_EXPORT(derived20)
BOOST_CLASS_EXPORT(derived40)
BOOST_CLASS_EXPORT(base)
int main() {
std::stringstream ss;
{
boost::archive::text_oarchive ar(ss);
base* d20 = new derived20();
ar << d20;
}
// prints:
// 22 serialization:archive 15 0 1 0
// 0 20
std::cout << ss.str() << std::endl;
{
boost::archive::text_iarchive iar(ss);
base *b = nullptr;
iar >> b;
b->print(); // Should print "20"
}
}
Prints
22 serialization::archive 17 0 9 derived20 1 0
0 20
20

boost::serialization with immutable abstract base and virtual inheritance

The code below is my current thinking to permit boost::serialization of an immutable abstract base with virtual inheritance. I hope I missed something and there is a simpler solution...?
As it stands, it raises a few questions:
Is the comment in IObject::serialize valid?
The comment in bs::save_construct_data for House seems to indicate a bug in boost::serialization. Is that correct, or is there a better way to do this?
Is there a more elegant way to deserialise Building then the Deserialise function combined with a protected constructor?
The result of (3) is that another Building implementation will necessitate a chunk of duplicated code. I suspect this will require a bit of CRTP to mitigate - any alternatives?
How does one make this work if the virtual base contains data members? I suspect this is similar (or identical) to the Building::Deserialise.
#include <fstream>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/serialization/export.hpp>
namespace bs = boost::serialization;
// IBase comes from an external library, and we are not interested in
// serialising an IBase*.
class IBase {
public:
virtual ~IBase(){};
};
class IObject : public virtual IBase {
private:
friend class bs::access;
template <class Archive>
void serialize(Archive &ar, const unsigned int version) {
std::cout << "Called IObject's serialize\n";
// IBase contains no members so there is no need to serialise it to/from the
// archive. However, the inheritance relationship must be registered in
// boost::serialization. We cannot use base_object to do this: It will try
// to static_cast *this to IBase, but we might not have created the instance
// yet, in which case there is no virtual table and a structured exception
// will be generated.
bs::void_cast_register<IObject, IBase>(static_cast<IObject *>(nullptr),
static_cast<IBase *>(nullptr));
}
public:
virtual ~IObject() {}
};
class IBuilding : public virtual IBase {
private:
friend class bs::access;
template <class Archive>
void serialize(Archive &ar, const unsigned int version) {
std::cout << "Called IBuilding's serialize\n";
bs::void_cast_register<IBuilding, IBase>(static_cast<IBuilding *>(nullptr),
static_cast<IBase *>(nullptr));
}
public:
virtual ~IBuilding() {}
};
/* Tedious forward declarations to permit later friending. */
class Building;
class House;
namespace boost {
namespace serialization {
template <class Archive>
inline void save_construct_data(Archive &ar, const Building *t,
const unsigned int version);
template <class Archive>
inline void save_construct_data(Archive &ar, const House *t,
const unsigned int version);
template <class Archive>
inline void load_construct_data(Archive &ar, House *t,
const unsigned int version);
}
}
/* Tedious forward declarations end. */
class Building : public IBuilding, public IObject {
private:
friend class bs::access;
template <class Archive>
void serialize(Archive &ar, const unsigned int version) {
std::cout << "Called Building's serialize\n";
// We can use base_object here because although the instance might not be
// created, the memory has been allocated. Since there is no virtual
// inheritance, the static_cast can succeed.
ar &bs::make_nvp("IObject", bs::base_object<IObject>(*this));
ar &bs::make_nvp("IBuilding", bs::base_object<IBuilding>(*this));
}
template <class Archive>
inline friend void bs::save_construct_data(Archive &ar, const Building *t,
const unsigned int version);
const double weight_;
protected:
const double height_;
// The Members, associated constructor, and Deserialise facilitate recreating
// this immutable base.
struct Members {
double weight_;
double height_;
};
Building(const Members &members)
: weight_(members.weight_), height_(members.height_) {}
template <class Archive> const Members Deserialise(Archive &ar) const {
double weight;
double height;
ar >> bs::make_nvp("weight_", weight) >> bs::make_nvp("height_", height);
return {weight, height};
}
public:
bool operator==(const Building &other) const {
return weight_ == other.weight_ && height_ == other.height_;
}
virtual double Height() const = 0;
};
class House : public Building {
private:
template <class Archive>
inline friend void bs::save_construct_data(Archive &ar, const House *t,
const unsigned int version);
template <class Archive>
inline friend void bs::load_construct_data(Archive &ar, House *t,
const unsigned int version);
template <class Archive>
explicit House(Archive &ar) : Building(Deserialise(ar)) {}
public:
House(double weight, double height) : Building({weight, height}) {}
virtual double Height() const { return height_; }
};
BOOST_CLASS_EXPORT(House);
namespace boost {
namespace serialization {
template <class Archive>
inline void save_construct_data(Archive &ar, const Building *t,
const unsigned int version) {
std::cout << "Called Building's save_construct_data\n";
ar << make_nvp("weight_", t->weight_) << make_nvp("height_", t->height_);
}
template <class Archive>
inline void bs::save_construct_data(Archive &ar, const House *t,
const unsigned int version) {
std::cout << "Called House's save_construct_data\n";
const auto &base = base_object<const Building>(*t);
ar << make_nvp("Building", base);
// ar << make_nvp("Building", &base); doesn't seem to work.
// Serialising out a reference calls Building's serialize method, the
// save_construct_data is only called for a pointer. This means we
// have to call it explicitly.
save_construct_data(ar, &base, version);
}
template <class Archive>
inline void bs::load_construct_data(Archive &ar, House *t,
const unsigned int version) {
std::cout << "Called House's load_construct_data\n";
ar >> make_nvp("Building", base_object<Building>(*t));
::new (t) House{ar};
}
}
}
int main() {
const char *file_name = "house.ser";
const bool save_first = true;
const House house(45367, 2.43);
std::cout << house.Height() << "\n";
const IObject *iHouse = &house;
if (save_first) {
std::ofstream ofs(file_name);
boost::archive::xml_oarchive oa(ofs);
oa << BOOST_SERIALIZATION_NVP(iHouse);
}
IBuilding *iHouse2;
{
std::ifstream ifs(file_name);
boost::archive::xml_iarchive ia(ifs);
ia >> BOOST_SERIALIZATION_NVP(iHouse2);
}
if (dynamic_cast<const Building &>(*iHouse) ==
dynamic_cast<const Building &>(*iHouse2))
std::cout << "ok\n";
else
std::cout << "uh oh\n";
return 0;
}
I don't believe that the comment is correct. I believe that the void_cast_register is un-necessary since IBase is known to be a virtual base class of IObject.
Futhermore, if you don't add the serialization_support free functions for IBase, you won't be able to serialise/deserialise an IBase*, only an IObject* (although I think that's fine unless you are managing ownership through the IBase rather than the IObject).

Input stream error when partially deserializing derived class into base class using Boost

When I serialize a derived class using boost and try to deserialize only the base part, I get input stream error. I guess my code is wrong. Is there a way to deserialize only the base part of a derived object using boost archive?
Reason for this code is that I am trying to implement a design to send derived objects from one process to another. The receiving process will look at the ID in the base part to decide which derived object is received.
This is the test code with which I am trying to verify that this is possible using boost, but I get input stream error on executing this
class DataIface
{
public:
DataIface()
:num(0)
{
}
DataIface( int num):
num(num)
{
}
int num;
template< class Archive >
void serialize( Archive& ar, const unsigned int version )
{
std::cout<<"Serializing base class \n"<<std::endl;
ar & num;
}
};
class Data1 : public DataIface
{
private:
friend class boost::serialization::access;
public:
Data1()
:a(0)
{
};
Data1( int a, int num):
DataIface(num),
a(a)
{
}
int a;
template< class Archive >
void serialize( Archive& ar, const unsigned int version )
{
std::cout<<"Serializing derived class \n"<<std::endl;
ar & boost::serialization::base_object<DataIface>(*this);
ar & a;
}
};
int main()
{
Data1 obj(10, 20);
std::ostringstream oss;
boost::archive::text_oarchive oa( oss );
oa << obj;
Data1 obj2;
std::istringstream iss(oss.str());
boost::archive::text_iarchive ia( iss );
ia >> obj2;
cout<< obj2.a << std::endl;
cout << obj2.num << std::endl;
DataIface iface;
try
{
ia >> iface;
}
catch(std::exception& e)
{
std::cout<<e.what()<<std::endl;
}
cout << iface.num << std::endl;
return 0;
}
Any help would be appreciated
This is the test code that I am trying to verify that this is possible using boost and I get input stream error
What is the conclusion?
The conclusion is: it doesn't work. That's because it's not a feature. No where in the documentation does it even suggest you can do this.
Runtime Polymorphism
Just use polymorphism as intended!
Live On Coliru
#include <iostream>
#include <sstream>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/export.hpp>
class DataIface {
public:
virtual ~DataIface() {}
template <class Archive> void serialize(Archive &ar, const unsigned int version) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
};
class Data1 : public DataIface {
friend class boost::serialization::access;
public:
Data1(int a=0) : a(a) {}
int a;
template <class Archive> void serialize(Archive &ar, const unsigned int version) {
std::cout << __PRETTY_FUNCTION__ << "\n";
ar &boost::serialization::base_object<DataIface>(*this);
ar &a;
}
};
class Data2 : public DataIface {
friend class boost::serialization::access;
public:
Data2(int b=0) : b(b) {}
int b;
template <class Archive> void serialize(Archive &ar, const unsigned int version) {
std::cout << __PRETTY_FUNCTION__ << "\n";
ar &boost::serialization::base_object<DataIface>(*this);
ar &b;
}
};
BOOST_CLASS_EXPORT(Data1)
BOOST_CLASS_EXPORT(Data2)
int main() {
DataIface* tests[] = { new Data1(10), new Data2(-10) };
for(auto testobj : tests)
{
std::ostringstream oss;
{
boost::archive::text_oarchive oa(oss);
oa << testobj;
}
{
std::istringstream iss(oss.str());
boost::archive::text_iarchive ia(iss);
DataIface* obj = nullptr;
ia >> obj;
if (Data1* obj1 = dynamic_cast<Data1*>(obj))
std::cout << "It's a Data1: " << obj1->a << "\n";
if (Data2* obj2 = dynamic_cast<Data2*>(obj))
std::cout << "It's a Data2: " << obj2->b << "\n";
}
}
for(auto ptr : tests) delete ptr;
}
Prints:
void Data1::serialize(Archive&, unsigned int) [with Archive = boost::archive::text_oarchive]
void DataIface::serialize(Archive&, unsigned int) [with Archive = boost::archive::text_oarchive]
void Data1::serialize(Archive&, unsigned int) [with Archive = boost::archive::text_iarchive]
void DataIface::serialize(Archive&, unsigned int) [with Archive = boost::archive::text_iarchive]
It's a Data1: 10
void Data2::serialize(Archive&, unsigned int) [with Archive = boost::archive::text_oarchive]
void DataIface::serialize(Archive&, unsigned int) [with Archive = boost::archive::text_oarchive]
void Data2::serialize(Archive&, unsigned int) [with Archive = boost::archive::text_iarchive]
void DataIface::serialize(Archive&, unsigned int) [with Archive = boost::archive::text_iarchive]
It's a Data2: -10
Static Polymorphism
Alternatively, use a variant. This saves you the hassle of manual dynamic allocations and the potential cost of virtual dispatch.
Live On Coliru
#include <iostream>
#include <sstream>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/variant.hpp>
class Data1 {
friend class boost::serialization::access;
public:
Data1(int a=0) : a(a) {}
int a;
template <class Archive> void serialize(Archive &ar, const unsigned int version) {
ar &a;
}
friend std::ostream& operator<<(std::ostream& os, Data1 const& d) {
return os << "It's a Data1: " << d.a;
}
};
class Data2 {
friend class boost::serialization::access;
public:
Data2(int b=0) : b(b) {}
int b;
template <class Archive> void serialize(Archive &ar, const unsigned int version) {
ar &b;
}
friend std::ostream& operator<<(std::ostream& os, Data2 const& d) {
return os << "It's a Data2: " << d.b;
}
};
int main() {
using V = boost::variant<Data1, Data2>;
V tests[] = { Data1{10}, Data2{-10} };
for(auto testobj : tests)
{
std::ostringstream oss;
{
boost::archive::text_oarchive oa(oss);
oa << testobj;
}
{
std::istringstream iss(oss.str());
boost::archive::text_iarchive ia(iss);
V deserialized;
ia >> deserialized;
std::cout << deserialized << "\n";
}
}
}
This prints:
It's a Data1: 10
It's a Data2: -10

Boost serialization of class handling a possible null pointer

I want to serialize the following class wrapping a pointer which can handle a null m_element as you can see when calling the default constructor. This follows this question.
Live MCVE on Coliru
template <typename T>
struct Ptr { // Ptr could use init constructor here but this is not the point
Ptr() { m_elem = 0; }
Ptr(const T* elem) {
if (elem)
m_elem = new T(*elem);
else
m_elem = 0;
}
Ptr(const T& elem)
{
m_elem = new T(elem);
}
Ptr(const Ptr& elem)
{
if (elem.m_elem)
m_elem = new T(*(elem.m_elem));
else
m_elem = 0;
}
virtual ~Ptr() { delete m_elem; m_elem = 0; };
const T& operator*() const { return *m_elem; };
T& operator*() { return *m_elem; };
const T* operator->() const { return m_elem; };
T* operator->() { return m_elem; };
T* m_elem;
};
namespace boost { namespace serialization {
// Not sure about the approach to manage null m_elem here
template<class Archive, class T>
void save(Archive & ar, const Ptr<T> &ptr, const unsigned int version)
{
T elem = 0;
if (ptr.m_elem != 0)
ar& boost::serialization::make_nvp("data", *ptr.m_elem);
else
ar& boost::serialization::make_nvp("data", elem);
}
// How to implement load ?
template<class Archive, class T>
void load(Archive & ar, Ptr<T> &ptr, const unsigned int version)
{
ar& boost::serialization::make_nvp("data", *ptr.m_elem);
}
template<class Archive, class T>
void serialize(Archive & ar, Ptr<T> &ptr, const unsigned int version)
{
boost::serialization::split_free(ar, ptr, version);
}
}} // end namespace
int main()
{
{
Ptr<A> p;
std::ostringstream oss;
boost::archive::xml_oarchive oa(oss);
oa << BOOST_SERIALIZATION_NVP(p);
std::cout << oss.str() << std::endl;
// segfault
Ptr<double> po;
std::istringstream iss;
iss.str(oss.str());
boost::archive::xml_iarchive ia(iss);
ia >> BOOST_SERIALIZATION_NVP(po);
}
{
Ptr<double> p(new double(2.0));
std::cout << *(p.m_elem) << std::endl;
std::ostringstream oss;
boost::archive::xml_oarchive oa(oss);
oa << BOOST_SERIALIZATION_NVP(p);
std::cout << oss.str() << std::endl;
// segfault
Ptr<double> po;
std::istringstream iss;
iss.str(oss.str());
boost::archive::xml_iarchive ia(iss);
ia >> BOOST_SERIALIZATION_NVP(po);
}
}
The serialization seems to work but the deserialization gives a segfault. I am working in C++0x.
How can I provide safe save and load functions to serialize Ptr without changing Ptr if possible ?
If I need to modify Ptr, what do you propose ?
Edit : thanks to Jarod42 comment I came up with the following save/load functions using a boolean to detect null pointer or not. Now I do not have a segfault anymore when m_elem is null but I have a one when it is not null.
template<class Archive, class T>
void save(Archive & ar, const Ptr<T> &ptr, const unsigned int version)
{
bool is_null;
if (ptr.m_elem != 0) {
is_null = false;
ar& boost::serialization::make_nvp("is_null", is_null);
ar& boost::serialization::make_nvp("data", *ptr.m_elem);
}
else
{
is_null = true;
ar& boost::serialization::make_nvp("is_null", is_null);
}
}
template<class Archive, class T>
void load(Archive & ar, Ptr<T> &ptr, const unsigned int version)
{
bool is_null;
ar& boost::serialization::make_nvp("is_null", is_null);
if (is_null == true) {
ptr.m_elem = 0;
}
else
{
ar& boost::serialization::make_nvp("data", *ptr.m_elem);
}
}
boost::archive's save and load methods understand the difference between pointers and object references. You don't need to specify *m_elem. m_elem will do (and work correctly). Boost will understand if the pointer is null and will simply store a value indicating a null pointer, which will be deserialised correctly.
(simplified) example:
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <iterator>
#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/split_free.hpp>
struct A {
A() : a(0) {}
A(int aa) : a(aa) {}
int a;
template <class Archive>
void serialize(Archive& ar, const unsigned int /*version*/)
{
ar& BOOST_SERIALIZATION_NVP(a);
}
};
std::ostream& operator<<(std::ostream& os, const A& a) {
os << "A{" << a.a << "}";
return os;
}
template <typename T>
struct Ptr { // Ptr could use init constructor here but this is not the point
Ptr()
: m_elem(0)
{}
Ptr(T elem)
: m_elem(new T(elem))
{
}
private:
// no copies
Ptr(const Ptr&);
Ptr& operator=(const Ptr&);
public:
// delete is a NOP when called with nullptr arg
virtual ~Ptr() { delete m_elem; };
T* get() const {
return m_elem;
}
T& operator*() const {
return *m_elem;
}
template <class Archive>
void serialize(Archive& ar, const unsigned int /*version*/)
{
ar& BOOST_SERIALIZATION_NVP(m_elem);
}
private:
T* m_elem;
};
template<class T>
std::ostream& operator<<(std::ostream& os, const Ptr<T>& p) {
if (p.get()) {
os << *p;
}
else {
os << "{nullptr}";
}
return os;
}
int main()
{
std::string payload;
{
Ptr<A> p;
std::cout << p << std::endl;
std::ostringstream oss;
boost::archive::xml_oarchive oa(oss);
oa << BOOST_SERIALIZATION_NVP(p);
payload = oss.str();
// std::cout << payload << std::endl;
Ptr<A> p2(A(6));
std::cout << p2 << std::endl;
oa << BOOST_SERIALIZATION_NVP(p2);
payload = oss.str();
// std::cout << payload << std::endl;
}
{
Ptr<A> po;
std::istringstream iss(payload);
boost::archive::xml_iarchive ia(iss);
ia >> BOOST_SERIALIZATION_NVP(po);
std::cout << po << std::endl;
Ptr<A> po2;
ia >> BOOST_SERIALIZATION_NVP(po2);
std::cout << po2 << std::endl;
}
}
expected output:
{nullptr}
A{6}
{nullptr}
A{6}
Thanks to Jarod42 comment,
You should serialize a boolean to know if pointer is nullptr or not
(and if not, serialize also its content). for loading, retrieve this
boolean and allocate a default element when needed that you load.
we came with the following save and load functions :
template<class Archive, class T>
void save(Archive & ar, const Ptr<T> &ptr, const unsigned int version)
{
bool is_null = !ptr.m_elem;
ar & boost::serialization::make_nvp("is_null", is_null);
if(!is_null) ar & boost::serialization::make_nvp("data", *ptr.m_elem);
}
template<class Archive, class T>
void load(Archive & ar, Ptr<T> &ptr, const unsigned int version)
{
bool is_null;
ar & boost::serialization::make_nvp("is_null", is_null);
if (!is_null) {
ptr.m_elem = new T;
ar& boost::serialization::make_nvp("data", *ptr.m_elem);
}
}
Live on Coliru
Had an issue in the load function when is_null is false. A storage is indeed needed for ptr in this case.

Boost.Serialization: Error on Calling Class Method during serialization process

I have the following problem when I try to call method in "TestSerialize" class during serialization process.
Here is my code:
class TestSerialize
{
public:
std::string GetVal() { return Val + "abc"; }
void SetVal(std::string tVal) { Val = tVal.substr(0, 2); }
protected:
std::string Val;
friend class boost::serialization::access;
template<class Archive> void save(Archive & ar, const unsigned int version) const
{
using boost::serialization::make_nvp;
std::string tVal = GetVal(); // Error here
ar & make_nvp("SC", tVal);
}
template<class Archive> void load(Archive & ar, const unsigned int version)
{
using boost::serialization::make_nvp;
std::string tVal;
ar & make_nvp("SC", tVal);
SetVal(tVal);
}
BOOST_SERIALIZATION_SPLIT_MEMBER();
};
int main()
{
TestSerialize tS;
std::ofstream ofs("test.xml");
boost::archive::xml_oarchive oa(ofs, boost::archive::no_header);
oa << BOOST_SERIALIZATION_NVP(tS);
ofs.close();
return 0;
}
The error that I encountered is:
'TestSerialize::GetVal' : cannot convert 'this' pointer from 'const TestSerialize' to 'TestSerialize &'
This error only happens on "save" but not "load"
I wonder why I get this error. I would like to know what Boost.Serialization do such that we have these two different behaviors.
I use Boost Library 1.47.0
save is a const function and can only call other const functions. GetVal isn't. Change it:
std::string GetVal() const { ... }