How to implement Boost::Serialize for Boost::Nested_Container - c++

(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.

Related

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_;
....

Search Tree - Iterator construction and non-pointer error

I'm trying to construct an iterator for a given tree, however I'm getting
error: base operand of '->' has non-pointer type 'SearchTree::Iterator'
I believe its due to the way i've tried to construct x but I'm not entirely sure what the problem is there.
All the headers and cpp files are from the book so there's nothing there that's incorrect. Its just given what they've given me, I can't figure out a way to construct this iterator.
Main.cpp
#include <iostream>
#include <string>
#include <vector>
#include "AVLTree.h"
#include "RandInt.h"
#include "SearchTree.h"
using std::cout;
using std::endl;
using std::string;
using std::to_string;
int main(int argc, const char* argv[]) {
// Set up random sequence generator
constexpr int maxv = 3249398;
RandInt rand {RandInt::getSeed(argc, argv), maxv};
AVLTree t{};
SearchTree st {};
AVLTree avl {};
// condition for duplicates
for (int i = 0; i < 5000 ; i++)
{
int random_value = rand();
string stringy = to_string(random_value);
if (t.find(k)==t.end())
{
t.insert (random_value, stringy);
}
else {}
}
SearchTree::Iterator x = SearchTree::Iterator(t.begin());
while (x != t.end()){
int k = x->key();
string str = x->value();
st.insert(k,str);
avl.insert(k,str);
++x;
}
return 0;
}
SearchTree.h
#ifndef __Binary_Search_Trees__SearchTree__
#define __Binary_Search_Trees__SearchTree__
#include <iostream>
#include <string>
#include <tuple>
#include <utility>
#include "AVLEntry.h"
#include "BinaryTree.h"
#include "Entry.h"
#include "Exception.h"
using std::string;
using ET = AVLEntry;
class SearchTree {
public:
using K = ET::Key;
using V = ET::Value;
class Iterator;
protected:
using BinTree = BinaryTree<ET>;
using TPos = BinTree::Position;
protected:
BinTree T;
int n;
long comparisons;
public:
SearchTree();
SearchTree(const SearchTree& st) = delete;
SearchTree& operator=(const SearchTree& st) = delete;
int size() const { return n; }
bool empty() const { return T.root().left().isExternal(); }
Iterator find(const K& k);
Iterator insert(const K& k, const V& x);
void restructure(TPos& x);
void erase(const K& k);
void erase(const Iterator& p);
Iterator begin() const;
Iterator end() const;
long getComparisons() const { return comparisons; }
void clearComparisons() { comparisons = 0L; }
void debugPrintTree(std::ostream& os, const string& name) const;
protected:
TPos root() const;
std::pair<TPos, int> finder(const K& k, TPos v);
TPos inserter(const K& k, const V& x);
TPos eraser(TPos& v);
constexpr static int indent {4};
void debugPrintTree(std::ostream& os, const TPos& subRoot, int depth) const;
void printIndented(std::ostream& os, const char* st, int depth) const;
void printIndented(std::ostream& os, const ET& val, int depth) const;
public:
class Iterator {
private:
TPos v;
public:
Iterator(const TPos& vv) : v(vv) {}
const ET& operator*() const { return *v; }
ET& operator*() { return *v; }
TPos pos() const { return v; }
bool operator==(const Iterator& p) const { return v == p.v; }
bool operator!=(const Iterator& p) const { return ! (*this == p); }
Iterator& operator ++();
friend class SearchTree;
};
};
#endif /* defined(__Binary_Search_Trees__SearchTree__) */
entry.h
#ifndef Binary_Search_Trees_Entry_h
#define Binary_Search_Trees_Entry_h
#include <iostream>
template <typename K, typename V>
class Entry {
public:
using Key = K;
using Value = V;
public:
Entry(const K& k = K(), const V& v = V()) : _key(k), _value(v) {}
const K& key() const { return _key; }
const V& value() const { return _value; }
void setKey(const K& k) { _key = k; }
void setValue(const V& v) { _value = v; }
private:
K _key;
V _value;
};
template<typename K, typename V>
std::ostream& operator<<(std::ostream& os, const Entry<K, V>& e)
{
return os << '(' << e.key() << ", " << e.value() << ')';
}
#endif

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.

serialize is not a member of std::unique_ptr

Is this question more appropriate for Boost forums? The complete code is referenced below and I do not consider that I have incorrectly attempted to convert an auto_ptr serialization example into a unique_ptr example as compared to other unique_ptr examples on this site. Thus, why would I receive a compilation error deep within a Boost library? I have used Boost serialization on standard containers before with no problems and, although this is a custom adapter, if similar examples have compiled, why not this?
I referenced http://www.boost.org/doc/libs/1_51_0/libs/serialization/example/demo_auto_ptr.cpp in an attempt to serialize my binary tree using a custom adaptor. Below is a code dump representing an sscce.
// disables conversion from 'std::streamsize' to 'size_t', possible loss of data
#pragma warning(disable:4244)
#ifndef BINARY_SEARCH_TREE_H_
#define BINARY_SEARCH_TREE_H_
#include<functional>
#include<memory>
#include<fstream>
#include<boost/archive/binary_oarchive.hpp>
#include<boost/archive/binary_iarchive.hpp>
#define BST_FILE_NAME "tree.dat" // default filename used to save and load
namespace boost {
namespace serialization {
template <class Archive, class T>
inline void save
(Archive &archive,
const std::unique_ptr<T> &subtree,
const unsigned int file_version)
{
// only the raw pointer has to be saved
const T *const subtree_x = subtree.get();
archive << subtree_x;
}
template <class Archive, class T>
inline void load
(Archive &archive,
const std::unique_ptr<T> &subtree,
const unsigned int file_version)
{
T *p_subtree;
archive >> p_subtree;
#if BOOST_WORKAROUND(BOOST_DINKUMWARE_STDLIB, == 1)
subtree.release();
subtree = std::unique_ptr< T >(p_subtree);
#else
subtree.reset(p_subtree);
#endif
}
template <class Archive, class T>
inline void serialize
(Archive &archive,
const std::unique_ptr<T> &subtree,
const unsigned int file_version)
{
boost::serialization::split_free(archive, subtree, file_version);
}
} // namespace serialization
} // namespace boost
template <class T = int>
class BinarySearchTree{
class BinarySearchTreeNode{
public:
std::unique_ptr<BinarySearchTreeNode> node_factory(const T &new_key, const T &new_index){
return std::unique_ptr<BinarySearchTreeNode>(new BinarySearchTreeNode(new_key, new_index)); }
BinarySearchTreeNode(BinarySearchTreeNode &&other) : key(other.key), index(other.index), left(std::move(other.left)),
right(std::move(other.right)) {key = index = left = right = nullptr; }
BinarySearchTreeNode &operator=(BinarySearchTreeNode &&rhs) { if(this != rhs) { key = rhs.key; index = rhs.index;
left = std::move(rhs.left); right = std::move(rhs.right);
rhs.key = rhs.index = rhs.left = rhs.right = nullptr;} return *this;}
~BinarySearchTreeNode() {} // Note to self; don't hide the destructor
friend class BinarySearchTree;
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive &archive, const unsigned int /* file_version */){
archive & key;
archive & index;
archive & left;
archive & right;
}
T key;
long index;
std::unique_ptr<BinarySearchTreeNode> left;
std::unique_ptr<BinarySearchTreeNode> right;
BinarySearchTreeNode() {}
BinarySearchTreeNode(const T &new_key, const T &new_index) :key(new_key), index(new_index),
left(nullptr), right(nullptr) {}
};
std::unique_ptr<BinarySearchTreeNode> root;
std::list<T> tree_keys;
std::list<long> tree_indicies;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive &archive, const unsigned int version){
archive & root;
}
BinarySearchTree(const BinarySearchTree &other){}
BinarySearchTree &operator=(const BinarySearchTree &rhs){}
std::unique_ptr<BinarySearchTreeNode> insert(const T &new_key, const T &new_index,
std::unique_ptr<BinarySearchTreeNode> &tree){
if(tree == nullptr){
return root->node_factory(new_key, new_index);
}else if(std::less<T> () (new_key, tree->key)){ // Left insertion
tree->left = insert(new_key, new_index, tree->left);
return std::move(tree);
}else { // Right insertion
tree->right = insert(new_key, new_index, tree->right);
return std::move(tree);
}
}
public:
BinarySearchTree() : root(nullptr) {}
BinarySearchTree(BinarySearchTree &&other) : root(std::move(other.root)) { other.root = nullptr; }
BinarySearchTree &operator=(BinarySearchTree &&rhs) { if(this != rhs) { root = std::move(rhs.root);
rhs.root = nullptr} return *this; }
bool insert_into_tree(const T &new_key, const T &new_index){
if(new_key == NULL){
return false;
}
root = std::move(insert(new_key, new_index, root));
return true;
}
void save(const BinarySearchTree &tree)
{
// create and open a binary archive for output
std::ofstream writer(BST_FILE_NAME, std::ofstream::out | std::ofstream::binary);
if(writer){
boost::archive::binary_oarchive serial_writer(writer);
//set_flags(0, true);
// write class instance to archive
serial_writer << tree;
// archive and stream closed when destructors are called
}else if(writer.fail()){
writer.clear();
}
}
void load(BinarySearchTree &tree)
{
// create and open a binary archive for output
std::ifstream reader(BST_FILE_NAME, std::ifstream::in | std::ifstream::binary);
if(reader){
boost::archive::binary_iarchive serial_reader(reader);
// read class state from archive
serial_reader >> tree;
// archive and stream closed when destructors are called
}else if(reader.fail()){
reader.clear();
}
}
~BinarySearchTree() {}
};
#endif
The code above compiles as does any usage of the tree member functions. Once choosing to call save, that is when the compiler error appears. Here is main:
#include<cstdlib>
#include "physical_view.h"
using namespace std;
int main(int argc, char *argv[]){
BinarySearchTree<> tree;
tree.insert_into_tree(10, 5);
tree.insert_into_tree(5, 15);
tree.insert_into_tree(15, 10); <--------- All is wonderful to here!
tree.save(tree); <---------- Compiler unhappy here!
return EXIT_SUCCESS;
}
How about that compiler error:
Error 1 error C2039: 'serialize' : is not a member of 'std::unique_ptr<_Ty>' c:\boost_12\include\boost-1_53_1\boost\serialization\access.hpp 118
I thank you for helping me resolve this compiler error.
The is that the signature of your serialize and load functions is incorrect. The std::unique_ptr<T> argument needs to be non-const for both of them. Since it can't deduce the types (due to the const), it simply ignores the overload and fails to find it at all.

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 { ... }