Как реализовать Boost :: Serialize для Boost :: Nested_Container - PullRequest
0 голосов
/ 03 февраля 2020

(продолжение другой вопрос .)

Boost :: Serialize часто доставляет исключение в oarchive, жалуясь, что повторное создание определенного объекта приведет к дублированию объектов. Некоторые архивы успешно сохраняются и перезагружаются, но многие приводят к ошибке выше. Я еще не смог определить точные условия, при которых возникает ошибка, но я доказал, что ни один из содержимого, используемого для заполнения nested_container и списка плоских объектов, не содержит повторяющихся идентификаторов объектов. Я использую текстовый архив, а не бинарный. Вот как я изменил код для nested_container, а также для другого, отдельного списка плоских объектов, чтобы выполнить 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 Ответ

0 голосов
/ 03 февраля 2020

Проблема, с которой вы сталкиваетесь, скорее всего, связана с определенным порядком, в котором элементы просматриваются в индексе № 0 (хешированном). Например, если мы заполним контейнер следующим образом:

nested_container c;
c.insert({54});
auto it=c.insert({0}).first;
  insert_under(c,it,{1});

Тогда элементы будут перечислены в индексе № 0 как (1, 54, 0). Ключевая проблема здесь заключается в том, что 1 является дочерним элементом 0: при загрузке элементов в том же порядке, в котором они были сохранены, первый равен 1, но для этого необходимо 0 до того, чтобы правильно указать на него. Это то, на что Boost.Serialization очень умно обнаруживает и жалуется. Такие ситуации «ребенок-родитель» зависят от очень непредсказуемого способа сортировки элементов в хешированном индексе, поэтому вы иногда видите проблему.

У вас есть два простых решения:

  1. Поменяйте местами индексы # 0 и # 1 в определении вашего вложенного контейнера: так как порядок сортировки индекса # 1 является предзаказом дерева, гарантируется, что родители обрабатываются раньше своих потомков.
  2. Переопределяет код сериализации вложенный контейнер для go через индекс # 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>());
  }
}

Полный демонстрационный код для решения № 2:

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

Кстати, почему вы предоставляете функции serialize для subtree_obj, path и * 1031? *? Вам не нужно это для сериализации nested_container s.

...