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

Non-intruisive Boost serialization of labelled enums C++

我想序列化结构 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);
}

}}

我尝试在本地向序列化函数引入查找 table :

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

不幸的是我得到了错误:

Error 78 error C2228: left of '.serialize' must have class/struct/union

因为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);
}

尽管我讨厌复活一个老问题,但我想做同样的事情,但需要在没有任何装饰器的情况下使枚举字符串可序列化。由于我没有找到太多关于该主题的其他信息,因此我将发布我的 hacky 解决方案作为其他需要将其枚举序列化为字符串的人的一个选项。

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

#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() 是枚举被破坏为整数的地方。

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

save_enum_type::invoke 大致在 boost 的调用堆栈中间被调用,最终进入 basic_text_oprimitive class。在那里,最终使用插入运算符将值保存到目标存档下面的 ostream 中。我们可以利用该实现细节,通过专门化 save_enum_type 和目标类型的 impelmenting 插入运算符,将我们的枚举类型存档为字符串。

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