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.
Related
(Followup of another question.)
Boost::Serialize often delivers an exception on oarchive, complaining that re-creating a particular object would result in duplicate objects. Some archives save and re-load successfully, but many result in the error above. I have not been able yet to determine the exact conditions under which the error occurs, but I have proven that none of the content used to populate the nested_container and the flat object list contains duplicate object IDs. I am using text archive, not binary. Here is how I have modified the code for nested_container and also for another, separate flat object list in order to do Boost::Serialize:
struct obj
{
int id;
const obj * parent = nullptr;
obj()
:id(-1)
{ }
obj(int object)
:id(object)
{ }
int getObjId() const
{
return id;
}
bool operator==(obj obj2)
{
if (this->getObjId() == obj2.getObjId())
return true;
else
return false;
}
#if 1
private:
friend class boost::serialization::access;
friend std::ostream & operator<<(std::ostream &os, const obj &obj);
template<class Archive>
void serialize(Archive &ar, const unsigned int file_version)
{
ar & id & parent;
}
#endif
};
struct subtree_obj
{
const obj & obj_;
subtree_obj(const obj & ob)
:obj_(ob)
{ }
#if 1
private:
friend class boost::serialization::access;
friend std::ostream & operator<<(std::ostream &os, const subtree_obj &obj);
template<class Archive>
void serialize(Archive &ar, const unsigned int file_version)
{
ar & obj_;
}
#endif
};
struct path
{
int id;
const path *next = nullptr;
path(int ID, const path *nex)
:id(ID), next(nex)
{ }
path(int ID)
:id(ID)
{ }
#if 1
private:
friend class boost::serialization::access;
friend std::ostream & operator<<(std::ostream &os, const path &pathe);
template<class Archive>
void serialize(Archive &ar, const unsigned int file_version)
{
ar & id & next;
}
#endif
};
struct subtree_path
{
const path & path_;
subtree_path(const path & path)
:path_(path)
{ }
#if 1
private:
friend class boost::serialization::access;
friend std::ostream & operator<<(std::ostream &os, const subtree_path &pathe);
template<class Archive>
void serialize(Archive &ar, const unsigned int file_version)
{
ar & path_;
}
#endif
};
//
// My flattened object list
//
struct HMIObj
{
int objId;
std::string objType;
HMIObj()
:objId(-1), objType("")
{ }
bool operator==(HMIObj obj2)
{
if (this->getObjId() == obj2.getObjId())
&& this->getObjType() == obj2.getObjType())
return true;
else
return false;
}
int getObjId() const
{
return objId;
}
std::string getObjType() const
{
return objType;
}
#if 1
private:
friend class boost::serialization::access;
friend std::ostream & operator<<(std::ostream &os, const HMIObj &obj);
template<class Archive>
void serialize(Archive &ar, const unsigned int file_version)
{
ar & objId & objType;
}
#endif
};
The problem you're experiencing is most likely due to, again, the particular order in which elements are traversed in index #0 (the hashed one). For instance, if we populate the container like this:
nested_container c;
c.insert({54});
auto it=c.insert({0}).first;
insert_under(c,it,{1});
Then the elements are listed in index #0 as (1, 54, 0). The crucial problem here is that 1 is a child of 0: when loading elements in the same order as they were saved, the first one is then 1, but this needs 0 to be loaded before in order to properly point to it. This is what Boost.Serialization very smartly detects and complains about. Such child-before-parent situations depend on the very unpredictable way elements are sorted in a hashed index, which is why you see the problem just sometimes.
You have two simple solutions:
Swap indices #0 and #1 in the definition of your nested container: as index #1 sorting order is the tree preorder, it is guaranteed that parents get processed before their children.
Override the serialization code of the nested container so as to go through index #1:
template<class Archive>
void serialize(Archive& ar,nested_container& c,unsigned int)
{
if constexpr(Archive::is_saving::value){
boost::serialization::stl::save_collection(ar,c.get<1>());
}
else{
boost::serialization::load_set_collection(ar,c.get<1>());
}
}
Complete demo code for solution #2 follows:
Live On Coliru
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/identity.hpp>
#include <boost/multi_index/member.hpp>
#include <iterator>
struct obj
{
int id;
const obj* parent=nullptr;
};
namespace boost{
namespace serialization{
template<class Archive>
void serialize(Archive& ar,obj& x,unsigned int)
{
ar&x.id&x.parent;
}
}} /* namespace boost::serialization */
struct subtree_obj
{
const obj& obj_;
};
struct path
{
int id;
const path* next=nullptr;
};
struct subtree_path
{
const path& path_;
};
inline bool operator<(const path& x,const path& y)
{
if(x.id<y.id)return true;
else if(y.id<x.id)return false;
else if(!x.next) return y.next;
else if(!y.next) return false;
else return *(x.next)<*(y.next);
}
inline bool operator<(const subtree_path& sx,const path& y)
{
const path& x=sx.path_;
if(x.id<y.id)return true;
else if(y.id<x.id)return false;
else if(!x.next) return false;
else if(!y.next) return false;
else return subtree_path{*(x.next)}<*(y.next);
}
inline bool operator<(const path& x,const subtree_path& sy)
{
return x<sy.path_;
}
struct obj_less
{
private:
template<typename F>
static auto apply_to_path(const obj& x,F f)
{
return apply_to_path(x.parent,path{x.id},f);
}
template<typename F>
static auto apply_to_path(const obj* px,const path& x,F f)
->decltype(f(x))
{
return !px?f(x):apply_to_path(px->parent,{px->id,&x},f);
}
public:
bool operator()(const obj& x,const obj& y)const
{
return apply_to_path(x,[&](const path& x){
return apply_to_path(y,[&](const path& y){
return x<y;
});
});
}
bool operator()(const subtree_obj& x,const obj& y)const
{
return apply_to_path(x.obj_,[&](const path& x){
return apply_to_path(y,[&](const path& y){
return subtree_path{x}<y;
});
});
}
bool operator()(const obj& x,const subtree_obj& y)const
{
return apply_to_path(x,[&](const path& x){
return apply_to_path(y.obj_,[&](const path& y){
return x<subtree_path{y};
});
});
}
};
using namespace boost::multi_index;
using nested_container=multi_index_container<
obj,
indexed_by<
hashed_unique<member<obj,int,&obj::id>>,
ordered_unique<identity<obj>,obj_less>
>
>;
#if 1 /* set to 0 to trigger pointer conflict exception */
#include <boost/serialization/set.hpp>
namespace boost{
namespace serialization{
template<class Archive>
void serialize(Archive& ar,nested_container& c,unsigned int)
{
if constexpr(Archive::is_saving::value){
boost::serialization::stl::save_collection(ar,c.get<1>());
}
else{
boost::serialization::load_set_collection(ar,c.get<1>());
}
}
}} /* namespace boost::serialization */
#endif
template<typename Iterator>
inline auto insert_under(nested_container& c,Iterator it,obj x)
{
x.parent=&*it;
return c.insert(std::move(x));
}
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <iostream>
#include <sstream>
void print(const nested_container& c)
{
for(const obj& x:c){
std::cout<<"("<<x.id;
if(x.parent)std::cout<<"->"<<x.parent->id;
std::cout<<")";
}
std::cout<<"\n";
}
int main()
{
nested_container c;
c.insert({54});
auto it=c.insert({0}).first;
insert_under(c,it,{1});
print(c);
std::ostringstream oss;
boost::archive::text_oarchive oa(oss);
oa<<c;
nested_container c2;
std::istringstream iss(oss.str());
boost::archive::text_iarchive ia(iss);
ia>>c2;
print(c2);
}
By the way, why are you providing serialize functions for subtree_obj, path and subtree_path? You don't need that to serialize nested_containers.
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()
};
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
How would you serialize/deserialize this class using boost::serialization?
#include <vector>
struct Foo {
struct Bar {
std::vector<int> * data; // Must point to Foo::data
Bar( std::vector<int> * d ) : data(d) { }
};
std::vector<int> data;
std::vector<Bar> elements;
Foo() {
// do very time consuming calculation to populate "data" and "elements"
}
};
The constructor in Foo must not be executed when the objected is loaded from the serialized data, but if the object is default constructed the constructor must be evaluated.
It is okay to add a default constructor to Bar, but after serialization the Foo::Bar::data must point to the Foo::data.
EDIT: Following is a non-working implementation of my attempt
This is my attempt based on the hints from #Matthieu. The problem is that when I deserialize Foo, I get no elements in Foo::data and Foo::elements.
struct Foo {
struct Bar {
std::vector<int> * data;
Bar( ) : data( 0 ) { }
Bar( std::vector<int> * d ) : data(d) { }
template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & data;
}
};
std::vector<int> data;
std::vector<Bar> elements;
Foo() {
std::cerr << "Running default constructor" << std::endl;
data.push_back(1);
data.push_back(2);
data.push_back(3);
data.push_back(4);
data.push_back(5);
elements.push_back( Bar( &data ) );
elements.push_back( Bar( &data ) );
elements.push_back( Bar( &data ) );
}
template<class Archive>
Foo( Archive & ar ) {
ar >> data; // is this corrent?
ar >> elements;
}
private:
BOOST_SERIALIZATION_SPLIT_MEMBER();
friend class boost::serialization::access;
template<class Archive>
void save(Archive & ar, const unsigned int version) const {
const std::vector<int> * data_ptr = &data;
// should data be seriliazed as pointer...
// it is used as a pointer in Bar
ar << data_ptr;
ar << elements;
}
};
int main(int argc, const char *argv[])
{
#if 0
// serialize
Foo foo;
boost::archive::text_oarchive oar(std::cout);
oar << foo;
#else
// deserialize
boost::archive::text_iarchive oar(std::cin);
Foo foo(oar);
#endif
std::cerr << foo.data.size() << std::endl;
std::cerr << foo.elements.size() << std::endl;
std::cerr << (&foo.data) << std::endl;
for( const auto& a : foo.data )
std::cerr << a << " ";
std::cerr << std::endl;
for( const auto& a : foo.elements)
std::cerr << a.data << " ";
std::cerr << std::endl;
return 0;
}
There is a section in the documentation that describes how to (de)serialize classes with non-default constructors. See here.
Basically, you must implement two functions called save_construct_data and load_construct_data in the namespace boost::serialization to write out and read in the data used to construct instances of your class. You can then call a non-default constructor of Foo from the load_construct_data function with the parameters necessary to reconstruct a Foo object.
Here is a working example based on your updated code:
Note that I've used shared_ptr's to clarify that the data member serialized by Foo and Bar are referencing the same thing.
#include <vector>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <sstream>
struct Foo {
struct Bar {
boost::shared_ptr< std::vector<int> > data; // Must point to Foo::data
Bar( boost::shared_ptr< std::vector<int> > d ) : data(d) { }
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
// ** note that this is empty **
}
};
boost::shared_ptr< std::vector<int> > data;
std::vector<Bar> elements;
Foo() : data( new std::vector<int>() ) {
std::cerr << "Running default constructor" << std::endl;
data->push_back(1);
data->push_back(2);
data->push_back(3);
data->push_back(4);
data->push_back(5);
elements.push_back( Bar( data ) );
elements.push_back( Bar( data ) );
elements.push_back( Bar( data ) );
}
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
// ** note that this is empty **
}
Foo(
boost::shared_ptr< std::vector<int> > const & data_,
std::vector<Bar> const & elements_ ) : data( data_ ), elements( elements_ )
{
std::cout << "cheap construction" << std::endl;
}
};
namespace boost { namespace serialization {
template<class Archive>
inline void save_construct_data(
Archive & ar, const Foo * foo, const unsigned int file_version
){
ar << foo->data << foo->elements;
}
template<class Archive>
inline void load_construct_data(
Archive & ar, Foo * foo, const unsigned int file_version
){
boost::shared_ptr< std::vector<int> > data;
std::vector<Foo::Bar> elements;
ar >> data >> elements;
::new(foo)Foo(data, elements);
}
template<class Archive>
inline void save_construct_data(
Archive & ar, const Foo::Bar * bar, const unsigned int file_version
){
ar << bar->data;
}
template<class Archive>
inline void load_construct_data(
Archive & ar, Foo::Bar * bar, const unsigned int file_version
){
boost::shared_ptr< std::vector<int> > data;
ar >> data;
::new(bar)Foo::Bar(data);
}
}}
int main()
{
std::stringstream ss;
{
boost::scoped_ptr< Foo > foo( new Foo() );
std::cout << "size before serialization is: " << foo->data->size() << std::endl;
boost::archive::text_oarchive oa(ss);
oa << foo;
}
{
boost::scoped_ptr< Foo > foo;
boost::archive::text_iarchive is(ss);
is >> foo;
std::cout << "size after deserialization is: " << foo->data->size() << std::endl;
}
return 0;
}
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 { ... }