我的建议是使用 ADL,或使用 Serialization Wrapper。
因为不知道什么时候停下来,所以做了下面的概念验证。一般往返测试允许:
- 允许切换存档类型
- 可选地提供包装函数(在我的示例中,我们将在下面定义
as_map 和as_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_map和as_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);
}