【问题标题】:C++ Boost binary serialization of std::map containing pointers constructed from Boost object_pool包含从 Boost object_pool 构造的指针的 C++ Boost std::map 二进制序列化
【发布时间】:2018-05-24 07:34:09
【问题描述】:

我的应用程序有一个类“MyClass”。它的对象是从 Boost Object_pool 构建的。

我需要通过 Boost Binary Serialization 序列化/反序列化包含这些对象的 std::map 作为值。

用于序列化 -

我从池中取出一个指针,做一些操作,将它插入到 std::map 并通过 Boost 二进制序列化对其进行序列化。

用于反序列化 -

我获取该序列化缓冲区并使用 Boost 二进制序列化对其进行反序列化。反序列化成功发生,但在此过程中,Boost Serialization 机制为指针分配了新的内存,该机制不是从对象池构造的。

因此,我将无法重用由 boost 序列化机制分配的内存,因为它不是从池中构造的,因此无法返回给对象池。

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/binary_object.hpp>
#include <boost/pool/object_pool.hpp>
#include <boost/serialization/map.hpp>
#include <map>
#include <iostream>
#include <sstream>
#include <string>
#include <functional>
#include <stdint.h>

class MyClass
{
  public :
   friend class boost::serialization::access;
   MyClass():data(0)
   {
     std::cout << std::endl << "MyClass()" << std::endl ;
   }

   MyClass( uint32_t val):data(val)
   {
     std::cout << std::endl << "Parameterized MyClass()" << std::endl ;
   }

    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
      ar & data;
    }

   ~MyClass(){}

    friend std::ostream &operator<<( std::ostream &output, const MyClass &D )
    {
      output << "Data : " << D.print() ;
      return output;
    }

   void print()
   {
      std::cout << std::endl << "Data : " << data << std::endl ;
   }


   private :
   uint32_t data ;
};

int main()
{ 
  try
  {
    typedef std::map<int, MyClass *> ObjectMap ;

    ObjectMap map;

     boost::object_pool<MyClass> pool ;

     map[1] = pool.construct(6) ;
     map[2] = pool.construct(7) ;
     map[3] = pool.construct(8) ;
     map[4] = pool.construct(9) ;

     // Serialization
     std::stringbuf strbuf;
      boost::archive::binary_oarchive oa( strbuf ) ;
      oa << map;


     // Deserialzation
      ObjectMap mapRoundTrip;

      boost::archive::binary_iarchive ia( strbuf ) ;
      ia >> mapRoundTrip ;
    }
    catch ( boost::archive::archive_exception &e )
    {
      std::cout << std::endl << e.what() << std::endl ;
    }
 }  

我的要求是在反序列化期间使用从 object_pool 获取的指针填充映射。

【问题讨论】:

  • 改名了? ;)
  • 是的,对这个问题有什么建议吗?正在尝试-load_construct_data。目前没有任何进展。
  • 是的,这是一个很好的练习。对boost::poolboost::serializationstd::map、参数打包等的使用不熟悉,所以学到了很多。
  • 对了,你为什么用std::map?你的键是顺序整数。你不能只使用std::vector吗?
  • 这是一个示例程序,这就是为什么键是简单的连续整数的原因。实际的代码/逻辑要复杂得多,而且键不是顺序的。

标签: c++ pointers serialization boost stdmap


【解决方案1】:

这是 Boost 序列化实现方式的限制。它不只是复制指针地址,而是取消引用指针并复制整个对象。这适用于所有 STL 容器。反序列化时,使用标准分配器创建一个新对象。

有两种方法可以避免这种情况:构建自定义地图类或使用 pool_allocator。


std::map 使用包装器

您可以通过不使用 STL 容器来避免这种情况。编写自己的地图(包装器)。例如

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/binary_object.hpp>
#include <boost/pool/object_pool.hpp>
#include <map>
#include <iostream>
#include <sstream>

class MyClass
{
public:
    friend class boost::serialization::access;
    MyClass() { std::cout << "MyClass()\n"; }
    MyClass(int val) :data(val) { std::cout << "MyClass(" << val << ")\n";  }

    template<class Archive>
    void serialize(Archive & ar, const unsigned int version) { ar & data; }

    void print() { std::cout << "Data : " << data << "\n"; }
private:
    int data;
};

template<class Key, class T>
class MyMap
{
public:
    MyMap(boost::object_pool<T> &pool) : mr_pool(pool) {}

    ~MyMap()
    {
        for (auto& kv : m_map)
        {
            if (kv.second != nullptr) mr_pool.destroy(kv.second);
            kv.second = nullptr;
        }
    }

    typename std::map<Key, T*>::iterator begin() { return m_map.begin(); }
    typename std::map<Key, T*>::iterator end() { return m_map.end(); }

    template<class ... Types>
    void construct(const Key& key, Types ... args)
    {
        m_map[key] = mr_pool.construct(args...);
    }

    template<class Archive>
    void save(Archive & ar, const unsigned int version) const
    {
        ar << m_map.size();
        for (auto& kv : m_map)
        {
            ar << kv.first;
            ar << boost::serialization::binary_object(kv.second, sizeof(T));
        }
    }

    template<class Archive>
    void load(Archive & ar, const unsigned int version)
    {
        size_t size;
        ar >> size;
        for (size_t i = 0; i<size; i++)
        {
            Key key;
            ar >> key;
            T* prt = mr_pool.construct();
            ar >> boost::serialization::make_binary_object(prt, sizeof(T));
            m_map[key] = prt;
        }
    }

    template<class Archive>
    void serialize(Archive & ar, const unsigned int file_version)
    {
        boost::serialization::split_member(ar, *this, file_version);
    }
private:
    boost::object_pool<T>& mr_pool;
    std::map<Key, T*> m_map;
};

int main()
{
    try
    {
        using ObjectMap = MyMap<int, MyClass>;

        boost::object_pool<MyClass> pool;
        ObjectMap map(pool);

        map.construct(1, 6);
        map.construct(2, 7);
        map.construct(3, 8);
        map.construct(4, 9);

        // Serialization
        std::stringbuf strbuf;
        boost::archive::binary_oarchive oa(strbuf);
        oa << map;

        for (auto& kv : map) {
            std::cout << "map: " << kv.first << ", ";
            kv.second->print();
        }
        std::cout << "pre destory\n";
        for (auto& kv : map) {
            std::cout << "map: " << kv.first << ", data addr: " << kv.second << "\n";
        }

        for (auto& kv : map) {
            if (kv.second != nullptr) pool.destroy(kv.second);
            kv.second = nullptr;
        }

        std::cout << "post destroy\n";
        for (auto& kv : map) {
            std::cout << "map: " << kv.first << ", data addr: "  << kv.second << "\n";
        }

        MyClass* temp = pool.construct(10); // to create memory offset
        // Deserialzation
        ObjectMap mapRoundTrip(pool);

        boost::archive::binary_iarchive ia(strbuf);
        ia >> mapRoundTrip;

        for (auto& kv : mapRoundTrip) {
            std::cout << "mapRoundTrip: " << kv.first << ", data addr: " << kv.second << "\n";
        }
        for (auto& kv : mapRoundTrip) {
            std::cout << "mapRoundTrip: " << kv.first << ", ";
            kv.second->print();
        }
        
        pool.destroy(temp);
        temp = nullptr;
    }
    catch (boost::archive::archive_exception &e)
    {
        std::cout << std::endl << e.what() << std::endl;
    }
    return 0;
}

输出:

MyClass(6)
MyClass(7)
MyClass(8)
MyClass(9)
map: 1, Data : 6
map: 2, Data : 7
map: 3, Data : 8
map: 4, Data : 9
pre destory
map: 1, data addr: 0x24ad720
map: 2, data addr: 0x24ad728
map: 3, data addr: 0x24ad730
map: 4, data addr: 0x24ad738
post destroy
map: 1, data addr: 0
map: 2, data addr: 0
map: 3, data addr: 0
map: 4, data addr: 0
MyClass(10)
MyClass()
MyClass()
MyClass()
MyClass()
mapRoundTrip: 1, data addr: 0x24ad728
mapRoundTrip: 2, data addr: 0x24ad730
mapRoundTrip: 3, data addr: 0x24ad738
mapRoundTrip: 4, data addr: 0x24ad740
mapRoundTrip: 1, Data : 6
mapRoundTrip: 2, Data : 7
mapRoundTrip: 3, Data : 8
mapRoundTrip: 4, Data : 9

DEMO


使用pool_allocator

您也可以不使用标准分配器,而是使用池分配器来规避这种情况。例如

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/binary_object.hpp>
#include <boost/pool/object_pool.hpp>
#include <boost/pool/pool_alloc.hpp>
#include <boost/serialization/map.hpp>
#include <map>
#include <iostream>
#include <sstream>

class MyClass
{
public:
    friend class boost::serialization::access;
    MyClass() { std::cout << "MyClass empty construct\n"; }
    MyClass(MyClass const& src) :data(src.data) { std::cout << "MyClass copy construct\n"; }
    void swap(MyClass& src) noexcept { std::swap(data, src.data); }
    MyClass(MyClass&& src) :MyClass() { src.swap(*this); std::cout << "MyClass move construct\n"; }
    
    MyClass(int val) :data(val) { std::cout << "MyClass data construct (" << val << ")\n"; }

    MyClass& operator=(MyClass src) { src.swap(*this); return *this; }

    template<class Archive>
    void serialize(Archive & ar, const unsigned int version) { ar & data; }

    void print() { std::cout << "Data : " << data << "\n"; }
private:
    int data;
};

int main()
{
    using ObjectMap = std::map<int, MyClass, std::less<int>, boost::pool_allocator<MyClass>>;
    using Pool = boost::singleton_pool<boost::pool_allocator_tag, sizeof(ObjectMap::value_type)>;

    try
    {
        ObjectMap map;

        map[1] = MyClass(6);
        map[2] = MyClass(7);
        map[3] = MyClass(8);
        map[4] = MyClass(9);

        // Serialization
        std::stringbuf strbuf;
        boost::archive::binary_oarchive oa(strbuf);
        oa << map;

        for (auto& kv : map) {
            std::cout << "map: " << kv.first << ", ";
            kv.second.print();
        }
        std::cout << "pre destory\n";
        for (auto& kv : map) {
            std::cout << "map: " << kv.first << ", data addr: " << &kv.second << "\n";
        }
        
        map.clear();
        Pool::purge_memory();

        map[5] = MyClass(10);

        std::cout << "post destroy and reassign\n";
        for (auto& kv : map) {
            std::cout << "map: " << kv.first << ", data addr: " << &kv.second << "\n";
        }

        // Deserialzation
        ObjectMap mapRoundTrip;

        boost::archive::binary_iarchive ia(strbuf);
        ia >> mapRoundTrip;

        for (auto& kv : mapRoundTrip) {
            std::cout << "mapRoundTrip: " << kv.first << ", data addr: " << &kv.second << "\n";
        }
        for (auto& kv : mapRoundTrip) {
            std::cout << "mapRoundTrip: " << kv.first << ", ";
            kv.second.print();
        }

        mapRoundTrip.clear();
    }
    catch (boost::archive::archive_exception &e)
    {
        std::cout << std::endl << e.what() << std::endl;
    }
    Pool::purge_memory();

    return 0;
}

输出:

MyClass data construct (6)
MyClass empty construct
MyClass data construct (7)
MyClass empty construct
MyClass data construct (8)
MyClass empty construct
MyClass data construct (9)
MyClass empty construct
map: 1, Data : 6
map: 2, Data : 7
map: 3, Data : 8
map: 4, Data : 9
pre destory
map: 1, data addr: 0x118e604
map: 2, data addr: 0x118e62c
map: 3, data addr: 0x118e654
map: 4, data addr: 0x118e67c
MyClass data construct (10)
MyClass empty construct
post destroy and reassign
map: 5, data addr: 0x118e604
MyClass empty construct
MyClass empty construct
MyClass move construct
MyClass empty construct
MyClass empty construct
MyClass move construct
MyClass empty construct
MyClass empty construct
MyClass move construct
MyClass empty construct
MyClass empty construct
MyClass move construct
mapRoundTrip: 1, data addr: 0x118e62c
mapRoundTrip: 2, data addr: 0x118e654
mapRoundTrip: 3, data addr: 0x118e67c
mapRoundTrip: 4, data addr: 0x118e6a4
mapRoundTrip: 1, Data : 6
mapRoundTrip: 2, Data : 7
mapRoundTrip: 3, Data : 8
mapRoundTrip: 4, Data : 9

您可以通过检查地址来了解内存池是如何被重用的。

DEMO

【讨论】:

  • 附注这可能可以清理。我不是最好的 C++ 程序员。
  • 为什么不简单地将池分配器与 std::map 一起使用?
  • @sehe 因为我不知道这是一种可能性。谢谢你指路。我现在知道了一些新的东西:)
  • 曾想过使用池分配器,但无法使用。对于简单的情况,它可以完美地工作,如解决方案中所示。在我的情况下,整个应用程序的内存需要控制逻辑/算法而不是地图。指向对象的指针需要执行大量指令,如果它通过了一些特定的标准,那么只有它会进入映射,否则它将返回到池中。而且有不止一个map需要插入这个指针,如果map使用pool_allocators,多个map将无法共享同一个内存池。
  • @Jerry 你想让我回滚到以前的版本吗,我有std::map-wrapper?我对你有价值吗?
猜你喜欢
  • 2018-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-13
  • 1970-01-01
  • 2011-03-02
  • 1970-01-01
  • 2015-09-05
相关资源
最近更新 更多