【问题标题】:Boost.Serialize: writing a general map serialization functionBoost.Serialize:写一个通用的map序列化函数
【发布时间】:2021-11-28 05:10:14
【问题描述】:

Boost.Serialize 为 std::map / std::multimap 提供显式序列化,这不适用于其他类似地图的容器。

我想序列化那些而不需要每次都重写这些函数,但 Boost 抱怨模棱两可。

这是我的代码:


/** Some sfinae to detect types that behave like std::map **/
template<typename T, typename = void>
struct is_map_ish : std::false_type { };

template<template<typename K, typename V> typename Map, typename K, typename V>
struct is_map_ish<
    Map<K,V>,
    std::void_t<
        typename Map<K,V>::key_type,
        typename Map<K,V>::value_type
    >
    > : std::true_type {
};

template<class Archive, class Map>
inline auto save(
    Archive & ar,
    const Map& t,
    const unsigned int /* file_version */,
        std::enable_if_t<is_map_ish<Map>::value>* = 0
) -> std::enable_if_t<is_map_ish<Map>::value>
{
    boost_155::serialization::stl::save_collection<
        Archive,
        Map
    >(ar, t);
}

template<class Archive, class Map>
inline auto load(
    Archive & ar,
    Map& t,
    const unsigned int /* file_version */,
        std::enable_if_t<is_map_ish<Map>::value>* = 0
) -> std::enable_if_t<is_map_ish<Map>::value> {
    boost_155::serialization::stl::load_collection<
        Archive,
        Map,
        boost_155::serialization::stl::archive_input_map<
            Archive, Map >,
            boost_155::serialization::stl::no_reserve_imp<Map>
    >(ar, t);
}

// split non-intrusive serialization function member into separate
// non intrusive save/load member functions
template<class Archive, class Map>
inline auto serialize(
    Archive & ar,
    Map &t,
    const unsigned int file_version,
    std::enable_if_t<is_map_ish<Map>::value>* = 0
) -> std::enable_if_t<is_map_ish<Map>::value> {
    boost_155::serialization::split_free(ar, t, file_version);
}

serialize 函数与

发生冲突

// default implementation - call the member function "serialize"
template<class Archive, class T>
inline void serialize(
    Archive & ar, T & t, const BOOST_155_PFTO unsigned int file_version
){
    access::serialize(ar, t, static_cast<unsigned int>(file_version));
}

在提升中,我显然无法改变。我尝试(在上面的代码中可见)将一些 SFINAE 放入返回类型和函数参数列表中,但这并不能帮助我避免重载歧义。

我有什么选择?在 C++20 中我会尝试概念,但在这里我只能使用 C++17。

【问题讨论】:

    标签: c++ serialization boost c++17 boost-serialization


    【解决方案1】:

    我的建议是使用 ADL,或使用 Serialization Wrapper

    因为不知道什么时候停下来,所以做了下面的概念验证。一般往返测试允许:

    • 允许切换存档类型
    • 可选地提供包装函数(在我的示例中,我们将在下面定义as_mapas_multimap);默认为恒等函数
    • 将 4 个条目的“map-ish 容器”序列化为字符串流
    • 将其反序列化
    • 将副本与原件进行比较
    template <typename OA, typename IA, template <typename...> class Map,
              typename WrapFun = std::identity>
    void roundtrip_impl(std::string_view archive_type, WrapFun apply_wrap = {})
    {
        using Container = Map<int, std::string>;
        Container original{{1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}};
    
        using namespace boost::archive;
        std::stringstream ss;
    
        {
            OA oa(ss, archive_flags::no_header);
            auto&& payload = apply_wrap(original);
            oa << boost::make_nvp("data", payload);
        }
    
        std::cout << archive_type << " "
                  << (typename boost::serialization::is_wrapper<decltype(
                              apply_wrap(original))>::type{}
                          ? "Wrapped "
                          : "Builtin ")
                  << std::setw(3) << ss.str().length() << " bytes" << std::flush;
    
        {
            IA ia(ss, archive_flags::no_header);
            Container replica;
            auto&& payload = apply_wrap(replica);
            ia >> boost::serialization::make_nvp("data", payload);
    
            std::cout << " - roundtrip verified: " << std::boolalpha
                      << (original == replica) << std::endl;
        }
    }
    

    现在为了系统化,我们将定义一个测试所有存档类型的入口点:

    template <template <typename...> class Map, typename WrapFun = std::identity>
    void roundtrips(WrapFun do_wrap = {})
    {
        roundtrip_impl<
            boost::archive::binary_oarchive,
            boost::archive::binary_iarchive,
            Map>("Binary", do_wrap);
        roundtrip_impl<
            boost::archive::text_oarchive,
            boost::archive::text_iarchive,
            Map>("Text  ", do_wrap);
        roundtrip_impl<
            boost::archive::xml_oarchive,
            boost::archive::xml_iarchive,
            Map>("XML   ", do_wrap);
    }
    

    这特别验证了包装器在 NVP 包装器存在的情况下工作。

    序列化包装器

    对于没有内置库支持的 map-ish 容器,让我们创建一个包装器:

    namespace Serialization {
        template <typename Map, typename Unique = std::true_type> struct map_wrapper {
            Map& ref;
            map_wrapper(Map& m) : ref(m) {}
    
            constexpr static bool is_unique = Unique::value;
        };
    

    添加一些便利功能as_mapas_multimap

    struct {
        constexpr auto operator()(auto& ref) const { return map_wrapper(ref); }
    } static inline constexpr as_map;
    
    struct {
        constexpr auto operator()(auto& ref) const {
            return map_wrapper<decltype(ref), std::false_type>(ref);
        }
    } static inline constexpr as_multimap;
    

    现在我们可以为包装器实现通用的保存/加载:

    // ADL finds these
    template <typename Ar, typename... Args>
    void save(Ar& ar, map_wrapper<Args...> const& w, unsigned v) {
        size_t n = std::size(w.ref);
        ar & boost::make_nvp("size", n);
        for (auto& element : w.ref) {
            ar & boost::make_nvp("item", element);
        }
    }
    

    这很简单。当然,反序列化更加多样化:

    template <typename Ar, typename Map, typename Unique>
    void load(Ar& ar, map_wrapper<Map, Unique>& w, unsigned v)
    {
        using V = typename boost::range_value<Map>::type;
    
        w.ref.clear(); // optionally?
        size_t n;
        ar & boost::make_nvp("size", n);
    
        while (n--) {
            V element;
            ar & boost::make_nvp("item", element);
    
            // insert and fix object tracking!
            if constexpr (Unique()) {
                auto [it, inserted] = w.ref.insert(std::move(element));
                if (inserted)
                    ar.reset_object_address(std::addressof(*it), &element);
            } else {
                auto it = w.ref.insert(std::end(w.ref), std::move(element));
                ar.reset_object_address(std::addressof(*it), &element);
            }
        }
    }
    

    最重要的一点是我们修复对象地址以进行地址跟踪。见docs

    没有模板BOOST_SERIALIZATION_SPLIT_FREE,所以我们写:

    template <typename Ar, typename... Args>
    void serialize(Ar& ar, map_wrapper<Args...>& m, unsigned v)
    {
        if constexpr (typename Ar::is_saving())
            save(ar, m, v);
        else
            load(ar, m, v);
    }
    
    } // namespace Serialization
    

    快完成了。我们只需要帮助图书馆意识到我们是一个包装器:

    namespace boost::serialization {
        template <typename... Args>
        struct is_wrapper<Serialization::map_wrapper<Args...>> {
            using type = std::true_type;
        };
        // it's heinous, but we have to do it for const qualified too
        template <typename... Args>
        struct is_wrapper<Serialization::map_wrapper<Args...> const> {
            using type = std::true_type;
        };
    } // namespace boost::serialization
    

    运行一些测试

    Live On Coliru

    // bunch of tests with various containers
    

    #include #include #include #include #include #include

    // including fantasy "maps"
    template <typename K, typename V>
        using FakeMap = boost::container::set<std::pair<K, V>>;
    template <typename K, typename V>
        using FakeMultiMap = std::vector<std::pair<K, V>>;
    
    int main() 
    {
        roundtrips<std::map>();
        roundtrips<std::multimap>();
    
        roundtrips<std::unordered_map>();
        roundtrips<std::unordered_multimap>();
    
        // no direct support, but we can wrap!
        using Serialization::as_map;
        using Serialization::as_multimap;
        roundtrips<boost::container::map>(as_map);
        roundtrips<boost::container::multimap>(as_multimap);
    
        roundtrips<boost::unordered_map>(as_map);
        roundtrips<boost::unordered_multimap>(as_multimap);
    
        roundtrips<FakeMap>(as_map);
        roundtrips<FakeMultiMap>(as_multimap);
    }
    

    打印

    Binary Builtin  85 bytes - roundtrip verified: true
    Text   Builtin  47 bytes - roundtrip verified: true
    XML    Builtin 393 bytes - roundtrip verified: true
    Binary Builtin  85 bytes - roundtrip verified: true
    Text   Builtin  47 bytes - roundtrip verified: true
    XML    Builtin 393 bytes - roundtrip verified: true
    Binary Builtin  93 bytes - roundtrip verified: true
    Text   Builtin  49 bytes - roundtrip verified: true
    XML    Builtin 425 bytes - roundtrip verified: true
    Binary Builtin  93 bytes - roundtrip verified: true
    Text   Builtin  49 bytes - roundtrip verified: true
    XML    Builtin 425 bytes - roundtrip verified: true
    Binary Wrapped  81 bytes - roundtrip verified: true
    Text   Wrapped  45 bytes - roundtrip verified: true
    XML    Wrapped 359 bytes - roundtrip verified: true
    Binary Wrapped  81 bytes - roundtrip verified: true
    Text   Wrapped  45 bytes - roundtrip verified: true
    XML    Wrapped 359 bytes - roundtrip verified: true
    Binary Wrapped  81 bytes - roundtrip verified: true
    Text   Wrapped  45 bytes - roundtrip verified: true
    XML    Wrapped 359 bytes - roundtrip verified: true
    Binary Wrapped  81 bytes - roundtrip verified: true
    Text   Wrapped  45 bytes - roundtrip verified: true
    XML    Wrapped 359 bytes - roundtrip verified: true
    Binary Wrapped  81 bytes - roundtrip verified: true
    Text   Wrapped  45 bytes - roundtrip verified: true
    XML    Wrapped 359 bytes - roundtrip verified: true
    Binary Wrapped  81 bytes - roundtrip verified: true
    Text   Wrapped  45 bytes - roundtrip verified: true
    XML    Wrapped 359 bytes - roundtrip verified: true
    

    警告

    请注意,在这些示例中,我假设 map-ish 容器将使用默认可构造的比较器/散列函数/相等比较器。否则,您必须记住实现 load_/save_construct_data,在这一点上,我相信您最好只编写特定于类型的实现。

    完整列表

    (反bitrot

    #include <boost/archive/basic_archive.hpp>
    #include <boost/serialization/nvp.hpp>
    #include <iostream>
    #include <iomanip>
    #include <sstream>
    #include <cassert>
    
    template <typename OA, typename IA, template <typename...> class Map,
              typename WrapFun = std::identity>
    void roundtrip_impl(std::string_view archive_type, WrapFun apply_wrap = {})
    {
        using Container = Map<int, std::string>;
        Container original{{1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}};
    
        using namespace boost::archive;
        std::stringstream ss;
    
        {
            OA oa(ss, archive_flags::no_header);
            auto&& payload = apply_wrap(original);
            oa << boost::make_nvp("data", payload);
        }
    
        std::cout << archive_type << " "
                  << (typename boost::serialization::is_wrapper<decltype(
                              apply_wrap(original))>::type{}
                          ? "Wrapped "
                          : "Builtin ")
                  << std::setw(3) << ss.str().length() << " bytes" << std::flush;
    
        {
            IA ia(ss, archive_flags::no_header);
            Container replica;
            auto&& payload = apply_wrap(replica);
            ia >> boost::serialization::make_nvp("data", payload);
    
            std::cout << " - roundtrip verified: " << std::boolalpha
                      << (original == replica) << std::endl;
        }
    }
    
    #include <boost/archive/binary_iarchive.hpp>
    #include <boost/archive/binary_oarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>
    #include <boost/archive/text_oarchive.hpp>
    #include <boost/archive/xml_iarchive.hpp>
    #include <boost/archive/xml_oarchive.hpp>
    
    template <template <typename...> class Map, typename WrapFun = std::identity>
    void roundtrips(WrapFun do_wrap = {})
    {
        roundtrip_impl<
            boost::archive::binary_oarchive,
            boost::archive::binary_iarchive,
            Map>("Binary", do_wrap);
        roundtrip_impl<
            boost::archive::text_oarchive,
            boost::archive::text_iarchive,
            Map>("Text  ", do_wrap);
        roundtrip_impl<
            boost::archive::xml_oarchive,
            boost::archive::xml_iarchive,
            Map>("XML   ", do_wrap);
    }
    
    #include <boost/range/value_type.hpp>
    
    namespace Serialization {
        template <typename Map, typename Unique = std::true_type> struct map_wrapper {
            Map& ref;
            map_wrapper(Map& m) : ref(m) {}
    
            constexpr static bool is_unique = Unique::value;
        };
    
        struct {
            constexpr auto operator()(auto& ref) const { return map_wrapper(ref); }
        } static inline constexpr as_map;
    
        struct {
            constexpr auto operator()(auto& ref) const {
                return map_wrapper<decltype(ref), std::false_type>(ref);
            }
        } static inline constexpr as_multimap;
    
        // ADL finds these
        template <typename Ar, typename... Args>
        void save(Ar& ar, map_wrapper<Args...> const& w, unsigned v) {
            size_t n = std::size(w.ref);
            ar & boost::make_nvp("size", n);
            for (auto& element : w.ref) {
                ar & boost::make_nvp("item", element);
            }
        }
    
        template <typename Ar, typename Map, typename Unique>
        void load(Ar& ar, map_wrapper<Map, Unique>& w, unsigned v)
        {
            using V = typename boost::range_value<Map>::type;
    
            w.ref.clear(); // optionally?
            size_t n;
            ar & boost::make_nvp("size", n);
    
            while (n--) {
                V element;
                ar & boost::make_nvp("item", element);
    
                // insert and fix object tracking!
                if constexpr (Unique()) {
                    auto [it, inserted] = w.ref.insert(std::move(element));
                    if (inserted)
                        ar.reset_object_address(std::addressof(*it), &element);
                } else {
                    auto it = w.ref.insert(std::end(w.ref), std::move(element));
                    ar.reset_object_address(std::addressof(*it), &element);
                }
            }
        }
    
        template <typename Ar, typename... Args>
        void serialize(Ar& ar, map_wrapper<Args...>& m, unsigned v)
        {
            if constexpr (typename Ar::is_saving())
                save(ar, m, v);
            else
                load(ar, m, v);
        }
    
    } // namespace Serialization
    
    namespace boost::serialization {
        template <typename... Args>
        struct is_wrapper<Serialization::map_wrapper<Args...>> {
            using type = std::true_type;
        };
        // it's heinous, but we have to do it for const qualified too
        template <typename... Args>
        struct is_wrapper<Serialization::map_wrapper<Args...> const> {
            using type = std::true_type;
        };
    } // namespace boost::serialization
    
    // bunch of tests with various containers
    #include <boost/serialization/map.hpp>
    #include <boost/serialization/unordered_map.hpp>
    #include <boost/container/map.hpp>
    #include <boost/container/flat_map.hpp>
    #include <boost/container/set.hpp>
    #include <boost/unordered_map.hpp>
    
    // including fantasy "maps"
    template <typename K, typename V>
        using FakeMap = boost::container::set<std::pair<K, V>>;
    template <typename K, typename V>
        using FakeMultiMap = std::vector<std::pair<K, V>>;
    
    int main() 
    {
        roundtrips<std::map>();
        roundtrips<std::multimap>();
    
        roundtrips<std::unordered_map>();
        roundtrips<std::unordered_multimap>();
    
        // no direct support, but we can wrap!
        using Serialization::as_map;
        using Serialization::as_multimap;
        roundtrips<boost::container::map>(as_map);
        roundtrips<boost::container::multimap>(as_multimap);
    
        roundtrips<boost::unordered_map>(as_map);
        roundtrips<boost::unordered_multimap>(as_multimap);
    
        roundtrips<FakeMap>(as_map);
        roundtrips<FakeMultiMap>(as_multimap);
    }
    

    【讨论】:

    • 哎呀。很高兴知道它是可以实现的,但是为此需要一个包装器类也是我所担心的:因为在实践中我只需要一种容器类型,所以这里正确的工程权衡似乎是不去通用在这种情况下,不需要包装器..当我需要它用于另一个容器时,这个代码库将能够使用 C++20 以及因此允许在不包装的情况下做我想做的事情的概念:|
    • 正如您正确观察到的那样,不需要包装器,所以真的没有哎哟。我不认为概念在这里改变任何东西(除非主要模板被删除)。如果他们这样做了,我认为它在概念上不会比所需的语法和可能的诊断有所改进
    • 我认为概念确实会发生变化,因为概念重载确实优先于通用重载:gcc.godbolt.org/z/ssbPf6T5c
    • 哦。我以为他们会“只是竞争”。感谢您的澄清。这似乎确实有帮助
    猜你喜欢
    • 2010-11-22
    • 2013-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-31
    相关资源
    最近更新 更多