标记枚举的非侵入式Boost序列化

Non-intruisive Boost serialization of labelled enums C++

本文关键字:Boost 序列化 枚举      更新时间:2023-10-16

我想序列化结构体A,在那里我可以以一种非侵入性的方式保存枚举表达式的标记名,而不是它的整数(而不必改变结构体A)。

enum e_fruit {
   apple,
   banana,
   coconut
};
struct A {
   e_fruit fruit;
   int     num;
};
namespace boost { namespace serialization {
template<class Archive>
void serialize(Archive & ar, A &a, const unsigned int version)
{
  ar & boost::serialization::make_nvp("FruitType", a.fruit); // this will store an integer
  ar & boost::serialization::make_nvp("Number", a.num);
}
}}

我已经尝试在本地引入一个查找表到序列化函数:

template<class Archive>
void serialize(Archive & ar, A &a, const unsigned int version)
{
  static const char* const labels[] = { "Apple", "Banana", "Coconut" };
  ar & boost::serialization::make_nvp("FruitType", labels[a.fruit]); // this won't work
   ar & boost::serialization::make_nvp("Number", a.num);
}

不幸的是,我得到了错误:

错误78错误C2228: left of '。序列化'必须有类/结构/联盟

,因为make_nvp的原型是

nvp< T > make_nvp(const char * name, T & t){
    return nvp< T >(name, t);
}

所以T应该是一个推导出来的模板参数。然后我考虑创建一个包含这些标签的结构体但是我必须在结构体a中添加这个这是我想要避免的。

那么我们如何才能以最不具侵入性的方式实现这一点呢?

我认为你需要将加载与保存分开。这个编译,我想知道它是否值得这个游戏…

struct fruit_serializer
{
    e_fruit &a_;
    fruit_serializer(e_fruit &a) : a_(a) {}
    template<class Archive>
    void save(Archive & ar, const unsigned int version) const
    {
        std::string label = labels[static_cast<int>(a_)];
        ar & boost::serialization::make_nvp("label", label);
    }
    template<class Archive>
    void load(Archive & ar, const unsigned int version)
    {
        std::string label ;
        ar & boost::serialization::make_nvp("label", label);
        a_ = static_cast<e_fruit>(std::find(labels.begin(), labels.end(), label) - labels.begin());
    }
    BOOST_SERIALIZATION_SPLIT_MEMBER();
    static std::vector<std::string> labels ;
};
std::vector<std::string> fruit_serializer::labels({ "Apple", "Banana", "Coconut" });
template<class Archive>
void serialize(Archive & ar, A &a, const unsigned int version)
{
    fruit_serializer a1(a.fruit);
    ar & boost::serialization::make_nvp("FruitType", a1);
}

尽管我不愿意重提一个老问题,但我想做同样的事情,但需要使枚举在没有任何装饰符的情况下可字符串序列化。由于我没有找到更多关于这个主题的其他内容,所以我发布了我的hack解决方案,作为任何需要将其枚举序列化为字符串的人的选择。

首先,一些(最终)序列化的示例类型:

#include <iostream>
#include <sstream>
#include <boost/serialization/nvp.hpp>
#include <boost/archive/xml_oarchive.hpp>

// A few dummy enum types to test the solution
enum MyEnum00 {
    Barb, Sarah, Lucy,
};
enum MyEnum01 {
    Corey, Trevor, Jacob = 99,
};
const char* const to_cstring(const MyEnum01 e) {
    switch (e) {
    case Corey:  return "Corey";
    case Trevor: return "Trevor";
    case Jacob:  return "Jacob";
    default:     return "UNKNOWN";
    }
}
inline std::ostream& operator<<(std::ostream& o, const MyEnum01 e) { return o << to_cstring(e); }

enum class MyEnumClass00 {
    Ricky, Julian, Bubbles
};
enum class MyEnumClass01 {
    Jim, Randy, Cyrus
};
const char* const to_cstring(const MyEnumClass01 e) {
    switch (e) {
    case MyEnumClass01::Jim:    return "I let the liquor do the thinking, bud!";
    case MyEnumClass01::Randy:  return "Got any cheeeeeseburgers?";
    case MyEnumClass01::Cyrus:  return "I got work to do";
    default:                    return "UNKNOWN";
    }
}
inline std::ostream& operator<<(std::ostream& o, const MyEnumClass01 e) { return o << to_cstring(e); }

在boost_1_63_0/boost/archive/detail/oserializer.hpp中,函数save_enum_type::invoke()是将枚举转换为int的地方。

Boost使用了模板结构和单独的模板成员的笨拙组合,因此很难在使用我们想要的枚举类型时应用我们的更改。作为一种解决方法,我们可以针对我们正在使用的归档类型专门化boost::archive::detail::save_enum_type。然后,我们可以重载它的invoke()函数,以防止它破坏我们需要作为字符串存档的枚举类型。

save_enum_type::invoke大致在boost的调用堆栈的中间被调用,它最终向下进入basic_text_primitive类。在这里,最后使用插入操作符将值保存到目标存档底层的ostream中。通过专门化save_enum_type并为目标类型实现插入操作符,可以利用该实现细节将枚举类型存档为字符串。

namespace boost {
    namespace archive {
        namespace detail {
            using xml_oarchive_ = boost::archive::xml_oarchive;
            using save_non_pointer_type_ = detail::save_non_pointer_type<xml_oarchive_>;
            template<>
            struct save_enum_type<xml_oarchive_>
            {
                // This is boost's stock function that converts enums to ints before serializing them.  
                // We've added a copy to our specialized version of save_enum_type to maintain the exisitng behavior for any 
                // enum types we don't care to have archived in string form
                template<class T>
                static void invoke(xml_oarchive_& ar, const T& t) {
                    const int i = static_cast<int>(t);
                    ar << boost::serialization::make_nvp(NULL, i);
                }

                ///// specialized enum types /////
                // You could probably reduce all the repeated code with some type-trait magic...
                static void invoke(xml_oarchive_& ar, const MyEnum00 &e) {
                    save_non_pointer_type_::invoke(ar, e);
                }
                static void invoke(xml_oarchive_& ar, const MyEnum01 &e) {
                    save_non_pointer_type_::invoke(ar, e);
                }
                // Won't work -- MyEnumClass00 doesn't have an insertion operator, so the underlying ostream won't know 
                // how to handle it
                //static void invoke(xml_oarchive_& ar, const MyEnumClass00 &e) {
                //  save_non_pointer_type_::invoke(ar, e);
                //}
                static void invoke(xml_oarchive_& ar, const MyEnumClass01 &e) {
                    save_non_pointer_type_::invoke(ar, e);
                }
            };
        } // namespace detail
    } // namespace archive
} // namespace boost

最后是测试所有内容的代码:

int main()
{
    std::stringstream outstream;
    boost::archive::xml_oarchive ar(outstream);
    MyEnum00 e00_0 = Barb;
    MyEnum00 e00_1 = Sarah;
    MyEnum00 e00_2 = Lucy;
    MyEnum01 e01_0 = Corey;
    MyEnum01 e01_1 = Trevor;
    MyEnum01 e01_2 = Jacob;
    MyEnumClass00 ec00_0 = MyEnumClass00::Ricky;
    MyEnumClass00 ec00_1 = MyEnumClass00::Julian;
    MyEnumClass00 ec00_2 = MyEnumClass00::Bubbles;
    MyEnumClass01 ec01_0 = MyEnumClass01::Jim;
    MyEnumClass01 ec01_1 = MyEnumClass01::Randy;
    MyEnumClass01 ec01_2 = MyEnumClass01::Cyrus;
    ar
        // regular enums get passed down as int even if you don't explicitly convert them
        << BOOST_SERIALIZATION_NVP(e00_0)
        << BOOST_SERIALIZATION_NVP(e00_1)
        << BOOST_SERIALIZATION_NVP(e00_2)
        // regular enums can also get special treatment
        << BOOST_SERIALIZATION_NVP(e01_0)
        << BOOST_SERIALIZATION_NVP(e01_1)
        << BOOST_SERIALIZATION_NVP(e01_2)
        // enum classes that aren't specialized pass down as ints
        << BOOST_SERIALIZATION_NVP(ec00_0)
        << BOOST_SERIALIZATION_NVP(ec00_1)
        << BOOST_SERIALIZATION_NVP(ec00_2)
        // enum classes can also get special treatment
        << BOOST_SERIALIZATION_NVP(ec01_0)
        << BOOST_SERIALIZATION_NVP(ec01_1)
        << BOOST_SERIALIZATION_NVP(ec01_2)
        ;
    std::cout << outstream.str() << std::endl;
    return 0;
}