c++ - 如何设计具有 "annotated"字段的类?

标签 c++ class-design field

假设我们有某种包含数百种消息类型的协议(protocol),我们希望通过 C++ 类对每种消息类型进行建模。由于每个类都应该能够自动处理每个字段,一个自然的解决方案是只使用 std::tuple。具有所有必需的类型:

std::tuple<int, double, char> message;

print(message);   // the usual variadic magic

这一切都很好。但是,现在我想给每个字段一个名称,并且我希望能够在我的代码中引用该字段时使用该名称,并获得它的文本表示。天真地,或者在 C 中,我可能会写:

struct Message
{
    int    header;
    double temperature;
    char   flag;
};

这样我们就失去了元组的递归自动处理能力,但我们可以按字面意思命名每个字段。在 C++ 中,我们可以通过枚举来完成这两种操作:

struct Message
{
    enum FieldID { header, temperature, flag };
    static const char * FieldNames[] = { "header", "temperature", "flag" };

    typedef std::tuple<int, double, char> tuple_type;

    template <FieldID I>
    typename std::tuple_element<I, tuple_type>::type & get()
    { return std::get<I>(data); }

    template <FieldID I>
    static const char * name() { return FieldNames[I]; }

    tuple_type data;
};

现在我可以说,Message m; m.get<Message::header>() = 12;等等,我可以递归遍历这些字段,让每个字段打印出自己的值,并以自己的名字为前缀,等等。


现在的问题是:我怎样才能有效地编写这样的代码,而不重复?

理想情况下,我希望能够这样说:

START_MESSAGE(Message)
ADDFIELD(int, header)
ADDFIELD(double, temperature)
ADDFIELD(char, flag)
END_MESSAGE

有没有什么办法可以结合预处理器、Boost 和 C++11,在不需要外部生成工具的情况下实现这样的功能? (我认为 Boost.Preprocessor 称之为“水平”和“垂直”重复。我需要以某种方式“转置”字段数据。)这里的关键特征是我永远不必重复任何信息,修改或添加一个字段只需要一次更改。

最佳答案

您可以使用 boost 的预处理器序列来执行此操作。

#define CREATE_MESSAGE(NAME, SEQ) ...

CREATE_MESSAGE(SomeMessage,
  (int)(header)
  (double)(temperature)
  (char)(flag)
)

您需要遍历每一对以生成定义。我手边没有任何示例代码,但如果有趣的话我可能会安排一些。

有一次我有一个类似这样的生成器,它还生成了字段的所有序列化。我有点觉得它有点过头了。我觉得该领域的具体定义和声明性访问者更直接。万一别人不得不在我之后维护代码,它就不那么神奇了。我显然不知道你的情况,只是在实现之后我仍然有所保留。 :)

再看一次 C++11 的特性会很酷,尽管我还没有机会。

更新:

还有一些问题需要解决,但这主要是可行的。

#include <boost/preprocessor.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/arithmetic/mod.hpp>
#include <boost/preprocessor/control/if.hpp>

#include <tuple>

#define PRIV_CR_FIELDS(r, data, i, elem) \
    BOOST_PP_IF(BOOST_PP_MOD(i, 2),elem BOOST_PP_COMMA,BOOST_PP_EMPTY)()

#define PRIV_CR_STRINGS(r, data, i, elem) \
    BOOST_PP_IF(BOOST_PP_MOD(i, 2),BOOST_PP_STRINGIZE(elem) BOOST_PP_COMMA,BOOST_P

#define PRIV_CR_TYPES(r, data, i, elem) \
    BOOST_PP_IF(BOOST_PP_MOD(i, 2),BOOST_PP_EMPTY,elem BOOST_PP_COMMA)()

#define CREATE_MESSAGE(NAME, SEQ) \
    struct NAME { \
        enum FieldID { \
            BOOST_PP_SEQ_FOR_EACH_I(PRIV_CR_FIELDS, _, SEQ) \
        }; \
        std::tuple< \
            BOOST_PP_SEQ_FOR_EACH_I(PRIV_CR_TYPES, _, SEQ) \
        > data;\
        template <FieldID I> \
            auto get() -> decltype(std::get<I>(data)) { \
                return std::get<I>(data); \
            } \
        template <FieldID I> \
            static const char * name() { \
                static constexpr char *FieldNames[] = { \
                    BOOST_PP_SEQ_FOR_EACH_I(PRIV_CR_STRINGS, _, SEQ) \
                }; \
                return FieldNames[I]; \
            } \
    };

CREATE_MESSAGE(foo,
        (int)(a)
        (float)(b)
    )

#undef CREATE_MESSAGE

int main(int argc, char ** argv) {

    foo f;
    f.get<foo::a>() = 12;

    return 0;
}

get 的 decltype 有问题。我还没有真正使用过元组来知道那里会发生什么。不过,我认为这与您如何生成类型或字段没有任何关系。

这是预处理器使用 -E 生成的内容:

struct foo { 
  enum FieldID { a , b , }; 
  std::tuple< int , float , > data;
  template <FieldID I> 
    auto get() -> decltype(std::get<I>(data)) { 
      return std::get<I>(data); 
  } 
  template <FieldID I> static const char * name() { 
    static constexpr char *FieldNames[] = { "a" , "b" , }; 
    return FieldNames[I]; 
  } 
};

关于c++ - 如何设计具有 "annotated"字段的类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9897074/

相关文章:

c++ - 仅在发布版本中出现异常

c++ - 如何在 C++ 的 System() Shell 脚本中使用逗号?

c++ - C++ 中有没有办法将给定类的函数限制为仅另一个类(不使用继承, friend )?

c# - 如何打印文本文件中隐藏字段的值?

php - 在自定义 WooCommerce 字段上设置值

c++ - 有没有办法查询 _com_ptr_t 对象的当前引用计数?

c++ - string.find ("") 找不到空格

c++ - 现代 C 和 C++ : it is possible to use one defined structure for other declared structure?

c++ - 用于提供正确的类指针行为(指向常量的指针)的替代类设计?

database - 在数据库中存储年龄的最佳方式,无 DOB