I'm using Boost 1.59.0 to serialize objects (let's call them Foo) in a std::forward_list<Foo>. However, I am unable to do this in cases where std::forward_list<Foo> is empty. To demonstrate my problem I have extended the "Very simple case" from Boost's serialization tutorial.
#include <fstream>
#include <unordered_map>
#include <forward_list>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/unordered_map.hpp>
#include <boost/serialization/forward_list.hpp>
class Foo
{
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int)
{
ar & a;
ar & b;
}
int a;
int b;
public:
Foo()
: a(-1), b(-1) {}
Foo(int new_a, int new_b)
: a(new_a), b(new_b) {}
};
typedef std::forward_list<Foo> TListType;
class gps_position
{
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int)
{
ar & degrees;
ar & minutes;
ar & seconds;
ar & my_list;
}
int degrees;
int minutes;
float seconds;
TListType my_list;
public:
gps_position(){};
gps_position(int d, int m, float s, TListType new_list) :
degrees(d), minutes(m), seconds(s), my_list(new_list)
{}
};
int main()
{
std::ofstream ofs("filename");
TListType my_list(0);
// my_list.push_front(Foo(-1,-1));
const gps_position g(35, 59, 24.567f, my_list);
{
// save data
boost::archive::text_oarchive oa(ofs);
oa << g;
}
gps_position newg;
{
std::ifstream ifs("filename");
boost::archive::text_iarchive ia(ifs);
ia >> newg;
}
return 0;
}
This code compiles with g++ 4.8.3, however running it throws:
terminate called after throwing an instance of
'boost::archive::archive_exception'
what(): input stream error
Aborted (core dumped)
If we add one element to the list by uncommenting this line
// my_list.push_front(Foo(-1,-1));
no exception is thrown and everything seems to work. If I replace std::forward_list<Foo> with std::forward_list<int> in the above code then the code also works. Additionally, using std::list<Foo> or std::vector<Foo> also works, so the problem seems to be exclusive to std::forward_list.
Question: How can I serialize empty std::forward_list<Foo> without inserting dummy Foo objects?
Try closing the ofstream before reading from the file. Best way, move ofs inside the "// save data" block:
{
// save data
std::ofstream ofs("filename");
boost::archive::text_oarchive oa(ofs);
oa << g;
}
My guess is, with no data the write is smaller than the ofstream buffer and the file is physically empty/incomplete at the time of reading.
This is a flaw in the "very simple case" example, if you ask me.
This is caused by a bug in boost (still there in boost 1.67, explanation and patch here: https://svn.boost.org/trac10/ticket/13563). It's triggered by certain combinations of compiler and default constructors in the list elements.
As a workaround you can serialize a flag indicating if the list is empty. You need to split the serialize function into 'save' and 'load', e.g.:
template<typename Archive>
void save(Archive& ar, const unsigned int version) const
{
ar << degrees;
ar << minutes;
ar << seconds;
bool empty = nodes_.empty(); // workaround
ar << empty;
if (!empty)
ar << nodes_;
}
template<typename Archive>
void load(Archive& ar, const unsigned int version)
{
ar >> degrees;
ar >> minutes;
ar >> seconds;
bool empty; // workaround
ar >> empty;
if (!empty)
ar >> my_list;
}
BOOST_SERIALIZATION_SPLIT_MEMBER()
Related
The following code compiles and seems to serialize properly (that is, the static is saved only once apparently). However, it has an 'input stream error' exception on restore:
#include <boost/serialization/tracking.hpp>
#include <boost/serialization/level.hpp>
#include <boost/serialization/array.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <iostream>
#include <fstream>
#include <array>
class SA {
std::array<char, 1024*1024> sbuf;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int ver) {
ar & boost::serialization::make_array(sbuf.data(), sbuf.size());
};
};
BOOST_CLASS_IMPLEMENTATION(SA, boost::serialization::object_serializable); // serialization_level
BOOST_CLASS_TRACKING(SA, boost::serialization::track_always); // tracking_level
class Foo {
char buf[1024];
inline static SA sxbuf;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int ver) {
ar & boost::serialization::make_array(buf, sizeof(buf));
ar & sxbuf;
};
};
class FooList {
std::array<Foo, 100> fool;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int ver) {
ar & boost::serialization::make_array(fool.data(), fool.size());
};
};
int
main()
{
const std::string filename{"foo.sav"};
FooList x;
std::ofstream out{filename, std::ios::binary};
boost::archive::binary_oarchive oa(out);
oa << x;
std::cout << "Saved\n";
std::ifstream ifs{filename};
boost::archive::binary_iarchive ia(ifs);
if (ifs.fail()) {
std::cerr << "couldn't open input file " << filename << "\n";
return 1;
}
ia >> x; // gives exception
std::cout << "Restored\n";
return 0;
}
The output is as follows:
Saved
terminate called after throwing an instance of 'boost::archive::archive_exception'
what(): input stream error
(I tried this on godbolt too, but -lboost_serialization didn't seem to work, so it wouldn't link properly... possibly my error there).
Any insight much appreciated...
This opens an archive before the writing end is finalized:
std::ofstream out{filename, std::ios::binary};
boost::archive::text_oarchive oa(out);
oa << x;
std::cout << "Saved\n";
std::ifstream ifs{filename};
Also ifs is missing the ios::binary flag.
Other than that, you can
drop make_array,
prefer std::array over T[] and
default the serialization level.
Live On Coliru
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/array.hpp>
#include <array>
#include <fstream>
#include <iostream>
class SA {
std::array<char, 1024 * 1024> sbuf;
friend class boost::serialization::access;
template <class Ar> void serialize(Ar &ar, unsigned) { ar &sbuf; }
};
BOOST_CLASS_TRACKING(SA, boost::serialization::track_always) // tracking_level
class Foo {
std::array<char, 1024> buf;
inline static SA sxbuf;
friend class boost::serialization::access;
template <class Ar> void serialize(Ar &ar, unsigned) { ar &buf &sxbuf; }
};
class FooList {
std::array<Foo, 100> fool;
friend class boost::serialization::access;
template <class Ar> void serialize(Ar &ar, unsigned) { ar &fool; }
};
int main() {
const std::string filename{"foo.sav"};
{
std::ofstream out(filename, std::ios::binary);
boost::archive::binary_oarchive oa(out);
FooList x{};
oa << x;
std::cout << "Saved\n";
}
{
std::ifstream ifs(filename, std::ios::binary);
boost::archive::binary_iarchive ia(ifs);
FooList x{};
ia >> x; // gives exception
std::cout << "Restored\n";
}
}
Prints
Saved
Restored
There are abstract I1 and derived C1.
There are abstract I2 and derived C2.
I1 have shared_ptr<I2>. How can I make them serializable using boost serializaton? I am attemtping do it, but my application get exception.
#include <sstream>
#include <boost/shared_ptr.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/serialization/shared_ptr.hpp>
struct I1
{
I1() {}
virtual ~I1() = 0 {}
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
}
};
struct C1 : I1
{
virtual ~C1() {}
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & boost::serialization::base_object<I1>(*this);
}
};
struct I2
{
virtual ~I2() = 0 {}
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & p;
}
boost::shared_ptr<I1> p;
};
struct C2 : I2
{
C2() { p = boost::shared_ptr<I1>(new C1); }
virtual ~C2() { }
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & boost::serialization::base_object<I2>(*this);
}
};
int main()
{
C2 c2;
std::string s;
std::stringstream ss(s);
boost::archive::binary_oarchive oa(ss);
oa.register_type<I1>();
oa.register_type<C1>();
oa.register_type<I2>();
oa.register_type<C2>();
oa << c2;
boost::archive::binary_iarchive ia(ss);
//ia.register_type<I1>(); // cannot instantiate abstract class
ia.register_type<C1>();
//ia.register_type<I2>(); // cannot instantiate abstract class
ia.register_type<C2>();
ia >> c2;
}
The boost serialization documentation says here interesting things about BOOST_CLASS_EXPORT:
... BOOST_CLASS_EXPORT ...
Hence, the need for export is implied by the usage of a derived class that is manipulated via a pointer or reference to its base class.
Your p pointer does exactly that. Adding these macros to your code also gets rid of the ugly explicit register_type() calls from your main, which is also nice :)
So, this code seems to compile and work in VS2014:
#include <sstream>
#include <boost/shared_ptr.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/export.hpp>
struct I1
{
I1() {}
virtual ~I1() = 0 {}
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
}
};
BOOST_CLASS_EXPORT(I1)
struct C1 : I1
{
virtual ~C1() {}
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & boost::serialization::base_object<I1>(*this);
}
};
BOOST_CLASS_EXPORT(C1)
struct I2
{
virtual ~I2() = 0 {}
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & p;
}
boost::shared_ptr<I1> p;
};
BOOST_CLASS_EXPORT(I2)
struct C2 : I2
{
C2() { p = boost::shared_ptr<I1>(new C1); }
virtual ~C2() { }
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & boost::serialization::base_object<I2>(*this);
}
};
BOOST_CLASS_EXPORT(C2)
int main()
{
C2 c2;
std::string s;
std::stringstream ss(s);
boost::archive::binary_oarchive oa(ss);
oa << c2;
boost::archive::binary_iarchive ia(ss);
ia >> c2;
}
It is interesting, though, that the statement from Boost docs is obviously not true for all compilers, and lots of code examples on the internets just don't work in VS2014.
I think you aren't supposed to register your pure virtual classes on the output archive either.
Alternatively, you can use export. After defining your classes, replace the rest of your code with this:
#include <boost/serialization/export.hpp>
BOOST_CLASS_EXPORT(C1)
BOOST_CLASS_EXPORT(C2)
int main()
{
C2 c2;
std::string s;
std::stringstream ss(s);
{
boost::archive::binary_oarchive oa(ss);
oa << c2;
}
boost::archive::binary_iarchive ia(ss);
ia >> c2;
}
I put the output archive into a separate block. I think it most likely works without it but I want to be sure that everything is flushed (via going out of scope).
Add
BOOST_SERIALIZATION_ASSUME_ABSTRACT(I1)
BOOST_SERIALIZATION_ASSUME_ABSTRACT(I2)
As per the documentation http://www.boost.org/doc/libs/1_39_0/libs/serialization/doc/traits.html#abstract
UPDATE
I've just verified with VS2013RTM and Boost 1_55, it JustWorks(TM), I've
removed the type registrations for abstract bases (they can never be concretely loaded from the archive anyways)
added
#pragma warning(disable: 4244)
#include <boost/config/warning_disable.hpp>
at the top of the file to silence known chatty warnings
Code compiled and runs without error. For good style, you should probably
serialize c2 through a pointer (stack variables can subtly derail object tracking)
Here's the full code I ended up with:
#pragma warning(disable: 4244)
#include <boost/config/warning_disable.hpp>
#include <sstream>
#include <boost/shared_ptr.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/shared_ptr.hpp>
struct I1
{
I1() {}
virtual ~I1() = 0 {}
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
}
};
struct C1 : I1
{
virtual ~C1() {}
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & boost::serialization::base_object<I1>(*this);
}
};
struct I2
{
virtual ~I2() = 0 {}
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & p;
}
boost::shared_ptr<I1> p;
};
struct C2 : I2
{
C2() { p = boost::shared_ptr<I1>(new C1); }
virtual ~C2() { }
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & boost::serialization::base_object<I2>(*this);
}
};
int main()
{
boost::shared_ptr<I2> c2(new C2);
std::string s;
std::stringstream ss(s);
boost::archive::text_oarchive oa(ss);
oa.register_type<C1>();
oa.register_type<C2>();
oa << c2;
std::cout << "Serialized form: '" << ss.str() << "'\n";
boost::archive::text_iarchive ia(ss);
ia.register_type<C1>();
ia.register_type<C2>();
ia >> c2;
}
And here's the output:
I am trying to serialize a class member. The following snippet of code will show the relevant class definitions and the non-intrusive serialization code I have included. I am getting a segfault during my saveHashTable() method which tries to serialize the class member shash_table_. Here is a self-contained trimmed version of the code:
#include <map>
#include <string>
#include <vector>
#include <set>
#include <fstream>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/set.hpp>
#include <boost/serialization/utility.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/list.hpp>
using namespace std;
struct ORROctree
{
public:
struct Node
{
public:
struct Data
{
public:
float n_[3], p_[3];
int id_x_, id_y_, id_z_, lin_id_, num_points_;
std::set<Node*> neighbors_;
void *user_data_;
};
public:
Node::Data *data_;
float center_[3], bounds_[6], radius_;
Node *parent_, *children_;
};
protected:
float voxel_size_, bounds_[6];
int tree_levels_;
Node* root_;
std::vector<Node*> full_leaves_;
};
struct ModelLibrary
{
template <typename T, typename REAL = float>
struct NDIMVoxelStructure
{
T *voxels_;
std::vector<int> total_num_of_voxels_i_;
std::vector<int> num_of_voxels_;
long total_num_of_voxels_;
std::vector<REAL> bounds_;
std::vector<REAL> spacing_;
std::vector<REAL> min_center_;
};
typedef std::pair<const ORROctree::Node::Data*, const ORROctree::Node::Data*> Dipole;
struct Base {
Dipole seg1;
Dipole seg2;
};
typedef std::list<Base> bases_list;
typedef std::map <string, bases_list> SerializeHashTableCell;
// MEMBER TO BE SERIALIZED
typedef NDIMVoxelStructure<SerializeHashTableCell> SerializeHashTable;
public:
SerializeHashTable shash_table_;
public:
bool saveHashTable();
bool loadHashTable();
};
// SERIALIZATION METHODS FOR THE TYPES USED TO FORM THE SERIALIZEHASHTABLE
namespace boost {
namespace serialization {
template<class Archive>
inline void serialize(Archive & ar, ModelLibrary::SerializeHashTable & h, const unsigned int version)
{
ar & h.total_num_of_voxels_;
ar & boost::serialization::make_array(h.voxels_, h.total_num_of_voxels_);
ar & h.num_of_voxels_;
ar & h.total_num_of_voxels_i_;
ar & h.bounds_;
ar & h.spacing_;
ar & h.min_center_;
}
template<class Archive>
inline void serialize(Archive & ar, ModelLibrary::Base & b, const unsigned int version)
{
ar & b.seg1;
ar & b.seg2;
}
template<class Archive>
inline void serialize(Archive & ar, ORROctree::Node n, const unsigned int version)
{
ar & n.data_;
ar & n.center_;
ar & n.bounds_;
ar & n.radius_;
ar & n.parent_;
ar & n.children_;
}
template<class Archive>
inline void serialize(Archive & ar, ORROctree::Node::Data d, const unsigned int version)
{
ar & d.id_x_;
ar & d.id_y_;
ar & d.id_z_;
ar & d.neighbors_;
ar & d.lin_id_;
ar & d.num_points_;
ar & d.p_;
}
}
}
bool ModelLibrary::saveHashTable ()
{
std::ofstream ofs("test.txt");
boost::archive::text_oarchive oa(ofs);
oa << shash_table_;
return true;
}
bool
ModelLibrary::loadHashTable ()
{
std::ifstream ifs("test.txt");
boost::archive::text_iarchive ia(ifs);
ia >> shash_table_;
return true;
}
int main()
{
ModelLibrary m;
m.saveHashTable();
}
You just need to initialize the data in your data structures. All primitive types that haven't been explicitely initialized will have indeterminate ("random") values. This includes the pointers, which means you're invoking Undefined Behaviour by dereferencing pointers to random memory locations.
Here's an update with the simplest initialization that could possibly work, and it runs clean under Valgrind. That's a good tool. Use it!
Also, crank up the compiler messages (-Wall -Wextra -pedantic at a minimum for gcc/clang).
Added in various places:
Data() : id_x_(0), id_y_(0), id_z_(0), lin_id_(0), num_points_(0), user_data_(0)
{
std::fill(n_, n_+3, 0);
std::fill(p_, p_+3, 0);
}
Node() : data_(0), radius_(0), parent_(0), children_(0)
{
std::fill(center_, center_+3, 0);
std::fill(bounds_, bounds_+6, 0);
}
ORROctree() : voxel_size_(0), tree_levels_(0), root_(0)
{
std::fill(bounds_, bounds_+6, 0);
}
NDIMVoxelStructure() : voxels_(0), total_num_of_voxels_(0)
{ }
Base() : seg1(0, 0), seg2(0, 0)
{ }
See it Live On Coliru
Update From the comment: the following lines were clearly in error:
inline void serialize(Archive & ar, ORROctree::Node n, const unsigned int version)
inline void serialize(Archive & ar, ORROctree::Node::Data d, const unsigned int version)
And should have read
inline void serialize(Archive & ar, ORROctree::Node& n, const unsigned int version)
inline void serialize(Archive & ar, ORROctree::Node::Data& d, const unsigned int version)
Again I have issue while serializing shared pointer using boost and below is code :
//Content.hpp file
#include <boost/serialization/string.hpp>
#include <boost\serialization\shared_ptr.hpp>
#include <boost/serialization/list.hpp>
struct Content
{
std::string type;
boost::shared_ptr<Content> mycontent; // mycontent is of type Content
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & id;
ar & name;
ar & mycontent;
}
public:
Content(void);
Content(const parameter_strings & parms);
~Content(void);
};
// Content.cpp file
Content::Content(void)
{
}
Content::~Content(void)
{
}
Content::Content(const parameter_strings & parms)
{
// implementation part
}
if I comment line " -- boost::shared_ptr mycontent; --" it compiles without error but i need to use shared_ptr and hence it gives error :
it gives error : error C4308: negative integral constant converted to unsigned type
I have included all required header files also but still issue exists.
I already answered this in the comments here:
#user3382670 making the destructor virtual enabled RTTI for your class. This means that typeid(variable) will return the proper runtime type (most derived class) insetad of the statically known type with pointers and references. – sehe Mar 22 at 1:01
Also, since you don't require polymorphism there should be no such problem in the first place: see it Live On Coliru
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/shared_ptr.hpp>
struct Content
{
std::string id, name;
boost::shared_ptr<Content> mycontent;
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive &ar, const unsigned int /*version*/)
{
ar & id;
ar & name;
ar & mycontent;
}
public:
Content() {}
typedef int parameter_strings;
Content(const parameter_strings & parms) { }
~Content() {}
};
int main()
{
boost::archive::text_oarchive ao(std::cout);
Content x;
ao << x;
}
The serialization example below is from the boost mailing list which is pretty much the same as what I would like to do. However, I have changed the archive so that it will serialize to XML. The compile does not fail if I serialize to binary, but it fails when serializing to xml. The compile fails in basic_xml_oarchive.hpp in the following method:
// boost code where compile fails
template<class T>
void save_override(T & t, BOOST_PFTO int)
{
// If your program fails to compile here, its most likely due to
// not specifying an nvp wrapper around the variable to
// be serialized.
BOOST_MPL_ASSERT((serialization::is_wrapper<T>));
this->detail_common_oarchive::save_override(t, 0);
}
It seems I haven't done enough to allow the std::map<int, CSomeData> object to be serialized, any ideas on how to fix this?
My serialization implementation:
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/serialization/map.hpp>
#include <fstream>
#include <string>
#include <map>
using namespace std;
// This is a test class to use as the map data.
class CSomeData {
public:
CSomeData(){};
CSomeData(float f0, string str0)
{
m_f0 = f0;
m_str0 = str0;
}
float m_f0;
string m_str0;
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & m_f0;
ar & m_str0;
}
};
// This is the class we really want to try serializing.
class CTest {
public:
CTest(){};
CTest(int nNumber)
{
m_nNumber = nNumber;
// Fill with some dummy data.
m_mTst.insert(make_pair(0, CSomeData(0.23f, "hi hi hi")));
m_mTst.insert(make_pair(1, CSomeData(7.65f, "second one")));
m_mTst.insert(make_pair(2, CSomeData(9.23f, "third one")));
m_mTst.insert(make_pair(3, CSomeData(5.6766, "chosen one")));
}
~CTest(){};
save()
{
std::ofstream ofs("filename");
// Write class instance to archive. Writing seems to work ok.
boost::archive::xml_oarchive oa(ofs);
oa << BOOST_SERIALIZATION_NVP(*this);
}
int m_nNumber;
private:
map<int, CSomeData> m_mTst;
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & m_nNumber;
ar & m_mTst;
}
};
I believe you need to tag the members with a name for XML serialisation. This specifies the element name to use in the XML. I.e. use something like:
ar & BOOST_SERIALIZATION_NVP(m_f0);
or (better in this case):
ar & make_nvp("field0", my_f0);
The tags will be ignored for binary serialisation. More details here:
http://www.boost.org/doc/libs/1_43_0/libs/serialization/doc/wrappers.html