【问题标题】:Non-intruisive Boost serialization of labelled enums C++标记枚举 C++ 的非侵入式 Boost 序列化
【发布时间】:2015-06-09 07:31:58
【问题描述】:

我想序列化结构 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:'.serialize' 左侧必须有 类/结构/联合

因为make_nvp的原型是

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

所以 T 应该是推导的模板参数。然后我考虑制作一个包含这些标签的结构,但是我必须在结构 A 中添加它,这是我想要避免的......

那么我们怎样才能以最非侵入性的方式实现这一点呢?

【问题讨论】:

  • 啊。我现在看到了编辑。 (最好在评论中引用它之前发布编辑:))
  • 是的,对不起,我会记住这一点;)

标签: c++ serialization boost


【解决方案1】:

我认为您需要将加载与保存分开。这样编译,不知道值不值得玩……

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

【讨论】:

  • 谢谢我目前正在尝试您的解决方案。如果我为 struct A 添加一个额外的成员,你能告诉我你会怎么做(见编辑后的帖子)?我不确定我是否应该将此成员包含在结构fruit_serializer 中。
  • @coincoin AFAICT 他已经做到了。还相关stackoverflow.com/questions/22533863/… 以及为什么它不能直接工作:stackoverflow.com/questions/29480452/…
  • 感谢 marom 我可以解决我的评论 :)。我只是将整个结构放在struct_serializer 中,即A &amp;a; 而不是e_fruit &amp;a_;。像魅力一样工作!
  • 一个可能的改进:使 enum->string 和 string->enum 的转换更加清晰。
【解决方案2】:

尽管我讨厌复活一个老问题,但我想做同样的事情,但需要在没有任何装饰器的情况下使枚举字符串可序列化。由于我没有找到有关该主题的其他内容,因此我将发布我的 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 类。在那里,插入运算符最终用于将值保存到目标存档下的 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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多