使用 boost 对带有自定义键的嵌套 unordered_map 进行序列化

serialization of a nested unordered_map with custom key using boost

我写了一个 class,其中一个成员是嵌套的 unordered_map,自定义类型作为键,另一个 unordered_map 作为值。

我想 serialize/deserialize 使用 boost::serialization 库的内部地图。

然而,即使代码完成了大部分,编译器returns这个错误代码(我把它删减并简化了一点):

class std::unordered_map<
  const MyClass<...>::key_type,
  unsigned int,
  MyClass<...>::key_hash,
  std::equal_to<const MyClass<...>::key_type>,
  std::allocator<std::pair<const MyClass<...>::key_type, unsigned int> > >’ has no member named ‘serialize’

据我了解,它告诉我这张地图没有成员 serialize。但是,我包含了应该解决此问题的 boost headers(但不是)。

我怀疑我在地图声明中将密钥声明为 const 的事实与库混淆,但我无法(也就是知识不够)确定这一点:当我删除const 限定符我最终遇到了一堆其他编译错误。

我的 class 的代码如下:

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/unordered_map.hpp>
#include <boost/serialization/utility.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <unordered_map>

template<typename A, typename B, typename C>
class MyClass
{
  public:
  using coord_type = another_serializable_and_tested_class;
  
  struct key_type
  {
    coord_type from;
    coord_type to;
    key_type(coord_type const& origin, coord_type const& destination):
    from(origin),
    to(destination)
    {}
    bool operator==(key_type const& other) const
    {
      return (
               other.from == this->from &&
               other.to == this->to
             );
    }
    
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        ar & from;
        ar & to;
    }
  }; // end struct key_type

  struct key_hash : public std::unary_function<key_type, std::size_t>
  {
   std::size_t operator()(const key_type& k) const
   {
     size_t res = 17;
     res = res * 31 + std::hash<coord_type>()( k.from );
     res = res * 31 + std::hash<coord_type>()( k.to );
     return res;
   }
  }; // end struct key_hash
  
  using forward_flow_type = std::unordered_map<time_type, std::unordered_map<const key_type, value_type, key_hash>>;

  // other members and functions, including the serialization/deserialization operations:
  void deserialize_layer(int t) const
  {
    const std::string forward_filename = get_forward_flow_archive_name(t);
    // create and open an archive for input
    std::ifstream forward_ifs(forward_filename, std::ios::binary);
    boost::archive::binary_iarchive forward_ia(forward_ifs);
    // read class state from archive
    std::unordered_map<const key_type, value_type, key_hash> forward_layer;
    forward_ia >> forward_layer;
    // archive and stream closed when destructors are called
    m_forward_flow.emplace(t,forward_layer);
  }
};

知道出了什么问题吗?

帮自己一个忙,清理你的类型:

using Layer = std::unordered_map<key_type, value_type, key_hash>;
using forward_flow_type = std::unordered_map<time_type, Layer>;

const 被误导了,这取决于容器的实现。

接下来,

void deserialize_layer(int t) const

被标记为const,然而你...

m_forward_flow.emplace(t, forward_layer);

那不会编译。所以,删除 const.

剩下的问题是您的密钥类型不是默认可构造的。最简单的方法就是添加:

key_type(coord_type const& origin      = {},
         coord_type const& destination = {})
    : from(origin)
    , to(destination)
{
}

复杂的方法是使用save_construct_data and load_construct_data实现序列化。

演示

这增加了足够的机器来完成充满演示数据的单个帧的往返:

Live On Coliru

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/functional/hash.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/unordered_map.hpp>
#include <boost/serialization/utility.hpp>
#include <fstream>
#include <iostream>
#include <unordered_map>

auto get_forward_flow_archive_name(int t)
{
    return "forward_flow_" + std::to_string(t) + ".dat";
}

struct another_serializable_and_tested_class {
    double x, y;
    void serialize(auto& ar, unsigned) { ar & x & y; }

    bool operator==(another_serializable_and_tested_class const&) const = default;

    friend size_t hash_value(another_serializable_and_tested_class const& o) {
        auto h = boost::hash_value(o.x);
        boost::hash_combine(h, boost::hash_value(o.y));
        return h;
    }

    friend std::ostream& operator<<(std::ostream& os, another_serializable_and_tested_class const& p)
    {
        return os << "(" << p.x << ", " << p.y << ")";
    }
};

template <typename A, typename B, typename C> class MyClass {
  public:
    using coord_type = another_serializable_and_tested_class;

    struct key_type {
        coord_type from;
        coord_type to;

        key_type(coord_type const& origin      = {},
                 coord_type const& destination = {})
            : from(origin)
            , to(destination)
        {
        }

        bool operator==(key_type const& other) const
        {
            return (other.from == this->from && other.to == this->to);
        }

        friend class boost::serialization::access;
        template <class Archive>
        void serialize(Archive& ar, const unsigned int version)
        {
            ar& from;
            ar& to;
        }
    }; // end struct key_type

    struct key_hash {
        std::size_t operator()(const key_type& k) const
        {
            size_t res = 17;
            boost::hash_combine(res, k.from);
            boost::hash_combine(res, k.to);
            return res;
        }
    }; // end struct key_hash

    using time_type = int; // stub
    using value_type = long; // stub

    using Layer = std::unordered_map<key_type, value_type, key_hash>;
    using forward_flow_type = std::unordered_map<time_type, Layer>;

    forward_flow_type m_forward_flow;

    // other members and functions, including the serialization/deserialization
    // operations:
    void serialize_layer(int t) const
    {
        const std::string forward_filename = get_forward_flow_archive_name(t);
        std::ofstream forward_ofs(forward_filename, std::ios::binary);
        boost::archive::binary_oarchive forward_oa(forward_ofs);
        forward_oa << m_forward_flow.at(t);
    }

    // other members and functions, including the serialization/deserialization
    // operations:
    void deserialize_layer(int t)
    {
        const std::string forward_filename = get_forward_flow_archive_name(t);
        // create and open an archive for input
        std::ifstream forward_ifs(forward_filename, std::ios::binary);
        boost::archive::binary_iarchive forward_ia(forward_ifs);
        // read class state from archive
        Layer forward_layer;
        forward_ia >> forward_layer;
        // archive and stream closed when destructors are called
        m_forward_flow.emplace(t, forward_layer);
    }
};

int main() { 
    using Object = MyClass<void, void, void>;
    {
        Object obj;

        auto& layer42 = obj.m_forward_flow.emplace(42, Object::Layer{}).first->second;

        for (auto& [k, v] : {
                std::tuple(
                        Object::key_type{
                        {1, 2}, // from
                        {3, 4}  // to
                        },
                        5),
                std::tuple(Object::key_type{{6, 7}, {8, 9}}, 10),
                std::tuple(Object::key_type{{11, 12}, {13, 14}}, 15),
                std::tuple(Object::key_type{{16, 17}, {18, 19}}, 20),
                std::tuple(Object::key_type{{21, 22}, {23, 24}}, 25),
                std::tuple(Object::key_type{{26, 27}, {28, 29}}, 30),
                })
        {
            layer42.emplace(k, v);
        }

        obj.serialize_layer(42);
    }
    {
        Object obj;
        obj.deserialize_layer(42);

        for (auto& [k, v] : obj.m_forward_flow.at(42)) {
            auto& [from,to] = k;
            std::cout << "From " << from << " to " << to << " value " << v << "\n";
        }
    }
}

版画

From (1, 2) to (3, 4) value 5
From (6, 7) to (8, 9) value 10
From (11, 12) to (13, 14) value 15
From (16, 17) to (18, 19) value 20
From (21, 22) to (23, 24) value 25
From (26, 27) to (28, 29) value 30

符合预期。该文件(在我的机器上)包含:

00000000: 1600 0000 0000 0000 7365 7269 616c 697a  ........serializ
00000010: 6174 696f 6e3a 3a61 7263 6869 7665 0f00  ation::archive..
00000020: 0408 0408 0100 0000 0000 0000 0006 0000  ................
00000030: 0000 0000 000d 0000 0000 0000 0000 0000  ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000050: 0000 0000 0000 3a40 0000 0000 0000 3b40  ......:@......;@
00000060: 0000 0000 0000 3c40 0000 0000 0000 3d40  ......<@......=@
00000070: 1e00 0000 0000 0000 0000 0000 0000 3540  ..............5@
00000080: 0000 0000 0000 3640 0000 0000 0000 3740  ......6@......7@
00000090: 0000 0000 0000 3840 1900 0000 0000 0000  ......8@........
000000a0: 0000 0000 0000 3040 0000 0000 0000 3140  ......0@......1@
000000b0: 0000 0000 0000 3240 0000 0000 0000 3340  ......2@......3@
000000c0: 1400 0000 0000 0000 0000 0000 0000 2640  ..............&@
000000d0: 0000 0000 0000 2840 0000 0000 0000 2a40  ......(@......*@
000000e0: 0000 0000 0000 2c40 0f00 0000 0000 0000  ......,@........
000000f0: 0000 0000 0000 1840 0000 0000 0000 1c40  .......@.......@
00000100: 0000 0000 0000 2040 0000 0000 0000 2240  ...... @......"@
00000110: 0a00 0000 0000 0000 0000 0000 0000 f03f  ...............?
00000120: 0000 0000 0000 0040 0000 0000 0000 0840  .......@.......@
00000130: 0000 0000 0000 1040 0500 0000 0000 0000  .......@........