How to implement Boost::Serialize for Boost::Nested_Container

匆匆过客 提交于 2020-03-05 06:04:29

问题


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

回答1:


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:

  1. 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.
  2. 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.



来源:https://stackoverflow.com/questions/60041846/how-to-implement-boostserialize-for-boostnested-container

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!