【问题标题】:serialization of a nested unordered_map with custom key using boost使用 boost 使用自定义键序列化嵌套的 unordered_map
【发布时间】:2021-12-23 14:35:47
【问题描述】:

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

我想使用boost::serialization 库对内部映射进行序列化/反序列化。

但是,即使代码大部分完成,编译器也会返回此错误代码(我将其删减并简化了一点):

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 标头(但事实并非如此)。

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

我的课的代码如下:

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

知道出了什么问题吗?

【问题讨论】:

  • 请注意,当密钥已经存在时,emplace 不会静默插入。您可能想要检查(通过返回值)

标签: c++ serialization boost unordered-map


【解决方案1】:

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

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

【讨论】:

  • Woooh 非常感谢您的广泛回答。我将删除键的 const 说明符。对于第二点,我声明成员可变:这样做的原因是 1)我需要尊重 const 接口 2)如果类用户在读取数据时不知道数据是序列化/反序列化的,那么它是“等价于函数是常数。我知道这可能是开放的辩论。对于第三点,你是对的,我在深夜发现它,但它确实与默认构造函数有关!我会仔细阅读你的演示代码。
  • 使用 const 快速和松散的一个危险是,在 C++ 中它经常被用来表示:线程安全(非变异)。因此,您希望std::mutexstd::atomic_bool 类型的成员是可变的,但仅此而已。当然,由您决定您“必须尊重”的界面不做这样的假设。
  • (关于 const-correctness 意味着 C++11 中的线程安全的开创性文章似乎是 GotW#95;另请参阅 news.ycombinator.com/item?id=5791561 链接到我记得第一次看到这个的 channel9 视频)
猜你喜欢
  • 2014-12-28
  • 1970-01-01
  • 2011-05-16
  • 2012-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多