How can boost::serialization be used with std::unique_ptr from C++ - c++

I am trying to use boost::serialization library to serialize a class (the class A in following code) which is including a std::unique_ptr member.
Environment:
OS: Windows 10 1909
IDE: Microsoft Visual Studio Community 2019 Version 16.4.4
Library: boost-serialization 1.72.0 (installed with vcpkg tool)
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/list.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/split_free.hpp>
#include <boost/serialization/unique_ptr.hpp>
#include <boost/serialization/vector.hpp>
#include <fstream>
#include <iostream>
#include <memory>
using namespace std;
using namespace boost;
class A
{
public:
A()
{
}
A(int input_size, int input_value) // Constructor
{
this->data = std::make_unique<int[]>(input_size);
this->size = input_size;
for (int loop_number = 0; loop_number < size; loop_number++) {
data[loop_number] = input_value;
}
}
std::unique_ptr<int[]> get_data()
{
// deep copy
auto return_data = std::make_unique<int[]>(size);
for (int loop_number = 0; loop_number < size; loop_number++) {
return_data[loop_number] = data[loop_number];
}
return return_data;
}
int get_size()
{
return this->size;
}
~A()
{
}
private:
std::unique_ptr<int[]> data;
int size;
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar& data;
ar& size;
}
};
int main()
{
// create and open a character archive for output
std::ofstream ofs("filename");
// create class instance
const A a_object(10, 5);
// save data to archive
{
boost::archive::text_oarchive oa(ofs);
// write class instance to archive
oa << a_object;
// archive and stream closed when destructors are called
}
// ... some time later restore the class instance to its orginal state
A load_from_file;
{
// create and open an archive for input
std::ifstream ifs("filename");
boost::archive::text_iarchive ia(ifs);
// read class state from archive
ia >> load_from_file;
// archive and stream closed when destructors are called
}
return 0;
}
The compiler throws C2440 error and the error message is 'initializing': cannot convert from 'int *' to 'const T (*const )'. It refers to %includePath%\boost\serialization\unique_ptr.hpp
template<class Archive, class T>
inline void save(
Archive & ar,
const std::unique_ptr< T > &t,
const unsigned int /*file_version*/
){
// only the raw pointer has to be saved
// the ref count is rebuilt automatically on load
const T * const tx = t.get();
ar << BOOST_SERIALIZATION_NVP(tx);
}
It seems that std::unique_ptr still serialize unsuccessfully in boost-serialization library (version 1.72.0)?
Moreover, is there any solution to serialize a class with std::unique_ptr members?

Based on the fact that std::unique_ptr doesn't stored size, ar& data; in the "serialize" template function which is used for saving/loading data members can't be serialized due to the count of data is unknown in this line.
template<class Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar& data; // <== the count of data is unknown
ar& size;
}
The iterating process (as shown as the following code) of smart pointer "data" is needed in order to access all valid data.
for (int i = 0; i < size; ++i)
ar& data[i];
Therefore, the save part is like below.
template<class Archive>
void save(Archive& ar, const unsigned int version) const
{
ar& size;
for (int i = 0; i < size; ++i)
ar& data[i];
}
When it comes to load part, it is important to allocate space of smart pointer before loading data to avoid Access Violation Error
template<class Archive>
void load(Archive& ar, const unsigned int version)
{
ar& size;
data = std::make_unique<int[]>(size); // <== allocate space of smart pointer first
for (int i = 0; i < size; ++i)
ar& data[i];
}
Note: The macro BOOST_SERIALIZATION_SPLIT_MEMBER() need to be put because save/load function is split.
Finally, the serialization part in class A could be done as the following code.
friend class boost::serialization::access;
template<class Archive>
void save(Archive& ar, const unsigned int version) const
{
ar& size;
for (int i = 0; i < size; ++i)
ar& data[i];
}
template<class Archive>
void load(Archive& ar, const unsigned int version)
{
ar& size;
data = std::make_unique<int[]>(size);
for (int i = 0; i < size; ++i)
ar& data[i];
}
BOOST_SERIALIZATION_SPLIT_MEMBER()
Reference: Boost serialization tutorial

Depending of the version of Boost you are using, you may need to implement your own non-intrusive adapter, something like this:
namespace boost {
namespace serialization {
template<class Archive, class T>
inline void save( Archive & ar, const std::unique_ptr< T > &t, const unsigned int /*file_version*/){
// only the raw pointer has to be saved
const T * const base_pointer = t.get();
ar & BOOST_SERIALIZATION_NVP(base_pointer);
}
template<class Archive, class T>
inline void load(Archive & ar,std::unique_ptr< T > &t, const unsigned int /*file_version*/){
T *base_pointer;
ar & BOOST_SERIALIZATION_NVP(base_pointer);
t.reset(base_pointer);
}
template<class Archive, class T>
inline void serialize(Archive & ar,std::unique_ptr< T > &t, const unsigned int file_version){
boost::serialization::split_free(ar, t, file_version);
}
} // namespace serialization
} // namespace boost
See this for a complete example.

Related

Boost serialization of a std::vector of pointers. Error: "unregistered class - derived class not registered or exported"

I am using boost to (de)serialize some classes. All is well except for a class which has astd::vector<Artefact*> member (see below for definitions). I have problems with vectors of pointers.
I can (de)serialize single instances of class Artefact perfectly.
I cannot serialize a class which has a vector of pointers to class Artefact.
The boost error when I try to do so is:
"unregistered class - derived class not registered or exported"
I am getting some output. Archive file:
22 serialization::archive 19 1 0
0 1 0
1 1 0
2 0 0 66 0
Class Definitions
#include <boost/serialization/vector.hpp>
#include <boost/serialization/map.hpp>
...
class Tag {
public:
bool tag = false;
};
class Artefact : public Tag {
public:
std::vector<enjyn_Vertex> vertices;
std::vector<unsigned int> indices;
std::vector<enjyn_Texture*> textures;
glm::vec3 position{ 0.0f };
glm::vec3 scale{ 1.0f };
glm::vec3 rotation{ 0.0f, 1.0f, 0.0f };
float rotation_degrees{ 0.0f };
glm::vec4 colour{ 1.0f, 1.0f, 1.0f, 1.0f };
...
std::vector<Artefact> artefacts; // note, no problem serializing std::vector<Artefact> here
...
};
//Mainly static data and function members
class State {
public:
...
static std::map<std::string, bool> state;
...
}
class Event {
public:
...
virtual void OnMouseFocus(); //Lots of virtual data members like this one here
..
}
//Large class with lots of data and function members.
class Enjyn : public Event, public State {
public:
...
std::vector<Artefact*> artefacts; // I believe this is the problem member
...
}
Free Non-Intrusive (De)Serialization Functions
I have non-intrusive free serialize functions for all classes I want to archive including
serialize functions for all base classes and non built in data members).
The serialize function below (de)serializes perfectly for single instances of Artefact:
#include <boost/serialization/export.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
...
void serialize(Archive& ar, Artefact& a, const unsigned int version) {
//ar.template register_type<Artefact>(); ; commented as classes exported see below
ar& boost::serialization::base_object<enjyn_Tag>(a);
ar& a.vertices;
ar& a.indices;
ar& a.textures;
ar& a.position;
ar& a.scale;
ar& a.rotation;
ar& a.rotation_degrees;
ar& a.colour;
ar& a.aabb;
ar& a.bsphere;
ar& a.tex_offset_x;
ar& a.tex_offset_y;
ar& a.tex_scale_x;
ar& a.tex_scale_y;
ar& a.name;
ar& a.artefacts; //works fine for vector of non-pointer vector of Artefact.
}
However when I try to archive an object with a std::vector<Artefact*>, I get:
"unregistered class - derived class not registered or exported"
void serialize(Archive& ar, Enjyn& e, const unsigned int version)
{
//ar.template register_type<enjyn>(); commented as classes exported see below:
ar& boost::serialization::base_object<Event>(e); // not sure whether this is necessary, no tracking requried of base classes.
ar& boost::serialization::base_object<State>(e);
ar.template register_type<Artefact>();
...
ar& e.artefacts; //This is a std::vector<Artefact*> which I cant archive
//if i remove this and add include any 'normal' non-pointer
//built in types the serialization appears to work perfectly.
...
}
Serializing Program Functions
I am serializing single instances of Artefact objects perfectly like this:
if (e.primary_artefact) { //primary_artefact is a pointer to an instance of Artefact
try {
std::string filepath = "test_filename";
std::ofstream o(filepath);
{
boost::archive::text_oarchive oa(o);
oa << *e.primary_artefact;
}
}
catch (std::ifstream::failure e) {
std::cerr << "ERROR:IFSTREAM: " << e.what() << std::endl;
}
catch (boost::archive::archive_exception e) {
std::cerr << "ERROR:BOOST SERIALISATION: " << e.what() << std::endl;
}
}
Attempting to serialize class Enjyn using this function creates error if I include Enjyn.artefacts (std::vector<Artefact*>) in the serialize function:
void serialize_enjyn(enjyn& e, std::string& name)
try {
std::string filepath = name;
std::ofstream o(filepath);
{
boost::archive::text_oarchive oa(o);
oa << e;
}
}
catch (std::ifstream::failure e) {
std::cerr << "ERROR:IFSTREAM: " << e.what() << std::endl;
}
catch (boost::archive::archive_exception e) {
std::cerr << "ERROR:BOOST SERIALISATION: " << e.what() << std::endl;
}
}
I went mad and exported GUIDS for all types in my main program file so all relevant types could be tracked within the archive. Not that this is particularly necessary. Only tracking of '''class Artefact''' is required.
#include <boost/serialization/export.hpp>
BOOST_CLASS_EXPORT_GUID(glm::vec2, "vec2")
BOOST_CLASS_EXPORT_GUID(glm::vec3, "vec3")
BOOST_CLASS_EXPORT_GUID(glm::vec4, "vec4")
BOOST_CLASS_EXPORT_GUID(Artefact, "enjyn_Artefact")
BOOST_CLASS_EXPORT_GUID(Vertex, "enjyn_Vertex")
BOOST_CLASS_EXPORT_GUID(Texture, "enjyn_Texture")
BOOST_CLASS_EXPORT_GUID(Tag, "enjyn_Tag")
BOOST_CLASS_EXPORT_GUID(State, "enjyn_State")
BOOST_CLASS_EXPORT_GUID(Event, "enjyn_event")
BOOST_CLASS_EXPORT_GUID(Enjyn, "enjyn")
int main(int argc, char* argv[]){
Discussion
Note, the Artefact object is used as a base for other classes. I have not registered those classes or written serialization functions for them as Artefact has no virtual functions, I am trying to serialize Artefact directly (though indirectly also via pointers), and the documentation for registration states:
It turns out that the kind of object serialized depends upon whether the base class (base in this case) is polymophic or not. If base is not polymorphic, that is if it has no virtual functions, then an object of the type base will be serialized. Information in any derived classes will be lost. If this is what is desired (it usually isn't) then no other effort is required.
The problem appears to be with the member std::vector<Artefact*> artefact in Enjyn. When I remove this from the serialize function for Enjyn, Enjyn serializes perfectly. I am obviously missing something fundemental to do with:
pointers, std::vector, and derived types and base classes from the boost documentation.
Please help!
If you actually took the time to make that a correct, self-contained example, you may find that there is no problem:
Live On Coliru
#include <boost/serialization/map.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/array.hpp>
#include <iostream>
// mocking stuff
namespace glm {
struct vec3 {
std::array<float, 3> a = {0,0,0};
bool operator==(vec3 const& rhs) const = default;
};
struct vec4 {
std::array<float, 4> a = {0,0,0,0};
bool operator==(vec4 const& rhs) const =default;
};
} // namespace glm
struct enjyn_Vertex {
bool operator==(enjyn_Vertex const&) const = default;
};
struct enjyn_Texture {
bool operator==(enjyn_Texture const&) const = default;
};
struct enjyn_Tag {
bool tag = false;
bool operator==(enjyn_Tag const&) const = default;
};
class Artefact : public enjyn_Tag {
public:
std::vector<enjyn_Vertex> vertices;
std::vector<unsigned int> indices;
std::vector<enjyn_Texture*> textures; // not owned, so leaked?
glm::vec3 position{0.0f};
glm::vec3 scale{1.0f};
glm::vec3 rotation{0.0f, 1.0f, 0.0f};
float rotation_degrees{0.0f};
glm::vec4 colour{1.0f, 1.0f, 1.0f, 1.0f};
std::vector<Artefact> artefacts;
bool operator==(Artefact const& rhs) const {
if (!enjyn_Tag::operator==(rhs))
return false;
auto tie = [](Artefact const& a) {
return std::tie(a.vertices, a.indices, /*a.textures,*/ a.position,
a.scale, a.rotation, a.rotation_degrees, a.colour,
a.artefacts);
};
if (tie(*this) != tie(rhs))
return false;
// compare textures indirectly
if (textures.size() != rhs.textures.size())
return false;
for (size_t i = 0; i<textures.size(); ++i)
if (*textures[i] != *rhs.textures[i])
return false;
return true;
}
private:
};
//Mainly static data and function members
class State {
public:
static std::map<std::string, bool> state;
bool operator==(State const&) const = default;
};
/*static*/ std::map<std::string, bool> State::state;
class Event {
public:
virtual void OnMouseFocus() {
} // Lots of virtual data members like this one here
bool operator==(Event const&) const = default;
};
//Large class with lots of data and function members.
class Enjyn
: public Event
, public State {
public:
// Rule Of Three...
~Enjyn() { for (auto a : artefacts) delete a; }
Enjyn() = default;
Enjyn(Enjyn const&) = delete;
Enjyn(Enjyn&&) = default;
Enjyn& operator=(Enjyn const&) = delete;
std::vector<Artefact*> artefacts;
bool operator==(Enjyn const& rhs) const {
if (! Event::operator==(rhs)) return false;
if (! State::operator==(rhs)) return false;
// compare artefacts indirectly
if (artefacts.size() != rhs.artefacts.size())
return false;
for (size_t i = 0; i<artefacts.size(); ++i)
if (*artefacts[i] != *rhs.artefacts[i])
return false;
return true;
}
};
#include <boost/serialization/export.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
namespace boost::serialization {
template <typename Archive> void serialize(Archive& ar, enjyn_Vertex&, unsigned) { }
template <typename Archive> void serialize(Archive& ar, enjyn_Texture&, unsigned) { }
template <typename Archive> void serialize(Archive& ar, enjyn_Tag& t, unsigned) { ar& t.tag; }
template <typename Archive> void serialize(Archive& ar, glm::vec3&v, unsigned) { ar& v.a; }
template <typename Archive> void serialize(Archive& ar, glm::vec4&v, unsigned) { ar& v.a; }
template <typename Archive> void serialize(Archive& ar, Artefact& a, unsigned)
{
//ar.template register_type<Artefact>(); ; commented as classes exported see below
ar& boost::serialization::base_object<enjyn_Tag>(a);
ar& a.vertices;
ar& a.indices;
ar& a.textures;
ar& a.position;
ar& a.scale;
ar& a.rotation;
ar& a.rotation_degrees;
ar& a.colour;
//ar& a.aabb;
//ar& a.bsphere;
//ar& a.tex_offset_x;
//ar& a.tex_offset_y;
//ar& a.tex_scale_x;
//ar& a.tex_scale_y;
//ar& a.name;
ar& a.artefacts; // works fine for vector of non-pointer vector of Artefact.
}
template <typename Archive> void serialize(Archive& ar, Event& e, unsigned)
{
}
template <typename Archive> void serialize(Archive& ar, Enjyn& e, unsigned)
{
ar& e.state; // warning: serializing statics may not do what you want
ar& e.artefacts;
}
} // namespace boost
Enjyn bake_me_one_with_everything();
int main() {
Enjyn const original = bake_me_one_with_everything();
std::stringstream ss;
{
boost::archive::text_oarchive oa(ss);
oa << original;
}
std::cout << ss.str() << "\n";
{
boost::archive::text_iarchive ia(ss);
Enjyn e;
ia >> e;
std::cout << "Roundtrip equal: " << std::boolalpha << (e == original)
<< std::endl;
}
}
#include <random>
Enjyn bake_me_one_with_everything()
{
Enjyn e;
std::mt19937 urbg{std::random_device{}()};
auto frand =
std::bind(std::uniform_real_distribution<float>{}, std::ref(urbg));
auto r10 =
std::bind(std::uniform_int_distribution<>{1, 10}, std::ref(urbg));
for (int i = 0; i < r10(); ++i) { // very sloppy loop
auto& a = *e.artefacts.emplace_back(new Artefact());
a.artefacts.resize(r10());
a.colour = {frand(), frand(), frand()};
a.position = {{frand(), frand(), frand()}};
a.rotation = {{frand(), frand(), frand()}};
a.rotation_degrees = frand() * 360;
a.scale = {{frand(), frand(), frand()}};
a.tag = r10() % 2;
std::generate_n(back_inserter(a.indices), r10(), r10);
a.vertices.assign(r10(), enjyn_Vertex{});
a.textures.assign(r10(), new enjyn_Texture{});
}
return e;
}
Prints things like
Your princess is in another castle.
Imaging The Flaws
I notice a lot of "gratuitous" use of inheritance (e.g. to inherit static data members), but specifically you inherit Enjyn from Event, which is a polymorphic type.
So my best guess is that this contains your actual issue.
Otherwise, you should be able to go from this live sample.
Caveats
Your code presentation shows some ordering that indicates you may be separating code across source files. In that case, don't fall into the trap of registering your types in a file that doesn't include all the relevant archive types BEFORE the export defitions: docs
BOOST_CLASS_EXPORT in the same source module that includes any of the archive class headers will instantiate code required to serialize polymorphic pointers of the indicated type to the all those archive classes. If no archive class headers are included, then no code will be instantiated.

How to serialize unique_ptr<char[]> in struct

I haven't been able to find any docs on how to serialize a unique_ptr to an array. Any help would be great.
struct Counter{
int index;
unique_ptr<char []> name;
template<class Archive>
void serialize(Archive & archive){
archive(index, name ); // serialize things by passing them to the archive
}
};
How it is assigned.
auto buffer = std::unique_ptr<char[]>(new char[BUFFER_SIZE]);
instance.name = std::move(buffer);
You can do it, but it needs some extra work. It differs depending on the type of the archive.
For text-based archives (e.g., XMLOutputArchive/XMLInputArchive and JSONOutputArchive/JSONInputArchive) you can use the saveBinaryValue() / loadBinaryValue() (http://uscilab.github.io/cereal/assets/doxygen/classcereal_1_1JSONOutputArchive.html).
Here's a complete example:
#include <iostream>
#include <memory>
#include <cereal/archives/xml.hpp>
#include <cereal/cereal.hpp>
struct Counter
{
static constexpr std::size_t BUFFER_SIZE = 12;
int index{};
std::unique_ptr<char[]> name;
Counter() = default;
Counter(int i)
: index{i},
name{new char[BUFFER_SIZE]}
{}
template<class Archive>
void load(Archive& archive)
{
archive(index);
name.reset(new char[BUFFER_SIZE]);
archive.loadBinaryValue(name.get(), BUFFER_SIZE * sizeof(decltype(name[0])));
}
template<class Archive>
void save(Archive& archive) const
{
archive(index);
archive.saveBinaryValue(name.get(), BUFFER_SIZE * sizeof(decltype(name[0])));
}
};
int main()
{
cereal::XMLOutputArchive archive(std::cout);
Counter c(42);
archive(CEREAL_NVP(c));
}
If you are using BinaryOutputArchive / BinaryInputArchive or PortableBinaryOutputArchive / PortableBinaryInputArchive the functions become saveBinary() and loadBinary() (http://uscilab.github.io/cereal/assets/doxygen/classcereal_1_1PortableBinaryOutputArchive.html).
For those, you can also wrap your array using binary_data():
template<class Archive>
void save(Archive& archive) const
{
archive(index, cereal::binary_data(name.get(), BUFFER_SIZE));
}

Serialization of non default constructible and std::reference wrapper alternatives

I tried serialising my (neural) network and am currently stuck-ish.
The issue seems to be that you can't serialize a std::reference_wrapper. I am unsure whether I should either change the way the references to the upper nodes are stored or come up with a way to serialize those.
Are there alternatives to reference_wrappers, which I neglected and still avoid c style pointers? (which are to be avoided as far as i know)
#include <iostream>
#include <fstream>
#include <list>
#include <vector>
#include <functional>
#include <boost/archive/tmpdir.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/utility.hpp>
#include <boost/serialization/list.hpp>
#include <boost/serialization/assume_abstract.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/vector.hpp>
typedef float nodeValueType;
typedef std::pair<std::vector<nodeValueType>, std::vector<nodeValueType>> Example;
typedef std::list<Example> ExampleList;
class Node;
class Link
{
public:
Link() = delete;
Link(Node& upperNode, Node& lowerNode)
: Link(upperNode, lowerNode, 1.0e-3f * (std::rand() / (nodeValueType) RAND_MAX))
{
}
Link(Node& upperNode, Node& lowerNode, nodeValueType weight)
: weight_(weight), upperNode_(upperNode), lowerNode_(lowerNode)
{
}
Link(const Link&) = delete;
Link& operator=(const Link&) = delete;
nodeValueType weight_;
std::reference_wrapper<Node> upperNode_;
std::reference_wrapper<Node> lowerNode_;
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, const unsigned int version){
ar & this->weight_;
ar & this->upperNode_;
ar & this->lowerNode_;
}
};
class Node
{
public:
Node();
// making it hard to copy us since we really never want to move.
// we are referred in loads of pointers
// therefore moving us invalidates all of them TODO invalidation scheme?
Node(const Node&) = delete;
Node& operator=(const Node&) = delete;
void linkTo(Node& other)
{
assert(this->lowerLinks_.max_size() > (this->lowerLinks_.size() + 1) * 2);
// Link creation
this->lowerLinks_.push_back(std::shared_ptr<Link>(new Link(*this, other)));
other.upperLinks_.push_back(std::shared_ptr<Link>(this->lowerLinks_.back()));
}
std::vector<std::shared_ptr<Link>> lowerLinks_;
std::vector<std::shared_ptr<Link>> upperLinks_;
// serialization
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, const unsigned int version){
ar & this->lowerLinks_;
}
};
int main()
{
Node n1;
Node n2;
n2.linkTo(n1);
std::string filename(boost::archive::tmpdir());
filename += "/demofile.txt";
std::ofstream ofs(filename);
boost::archive::text_oarchive oa(ofs);
oa << n1 << n2;
Node n3,n4;
// open the archive
std::ifstream ifs(filename);
boost::archive::text_iarchive ia(ifs);
// restore the schedule from the archive
ia >> n3 >> n4;
return 0;
}
There are 2 problems to overcome.
The first is that std::reference_wrapper cannot be null, so you might want to consider a 'nullable reference' type:
template<class T>
struct NullableReference
{
constexpr NullableReference() : ptr_(nullptr) {}
constexpr NullableReference(T& ref) : ptr_(std::addressof(ref)) {}
constexpr auto operator*() const -> T& { assert(!empty()); return *ptr_; }
constexpr operator T&() const { assert(!empty()); return *ptr_; }
constexpr bool empty() const { return !ptr_; }
template<class Archive>
void serialize(Archive& ar, unsigned int version)
{
ar & ptr_;
}
private:
T* ptr_;
};
The other is that Link does not have a default constructor (no doubt because of the non-nullable reference wrappers).
For this reason you may want to consider custom handling of the constructor when deserialising a Link (this is covered in the boost docs IIRC).
Of course, now that you're using a NullableReference, you can allow the default constructor for Link:
...
Link() : weight_(), upperNode_(), lowerNode_() {};
nodeValueType weight_;
NullableReference<Node> upperNode_;
NullableReference<Node> lowerNode_;
....

Is there a way to serialize iterators using boost serialization?

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()
};

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.