Ker*_* SB 9 c++ class-design field
想象一下,我们有一些具有数百种消息类型的协议,每种消息类型我们都希望通过C++类进行建模.由于每个类应该能够自动处理每个字段,因此一个自然的解决方案就是std::tuple拥有所有必需的类型:
std::tuple<int, double, char> message;
print(message); // the usual variadic magic
Run Code Online (Sandbox Code Playgroud)
这一切都很好.但是,现在我想给每个字段命名,我希望能够在引用代码中的字段时使用该名称,并获得它的文本表示.天真地,或者在C中,我可能写过:
struct Message
{
int header;
double temperature;
char flag;
};
Run Code Online (Sandbox Code Playgroud)
这样我们就失去了元组的递归自动处理能力,但我们可以从字面上命名每个字段.在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;
};
Run Code Online (Sandbox Code Playgroud)
现在我可以说Message m; m.get<Message::header>() = 12;等等,我可以在字段上进行递归,并使每个打印出自己的值,并以自己的名字为前缀等.
现在的问题是:如何有效地编写这样的代码而不重复?
理想情况下,我希望能够这样说:
START_MESSAGE(Message)
ADDFIELD(int, header)
ADDFIELD(double, temperature)
ADDFIELD(char, flag)
END_MESSAGE
Run Code Online (Sandbox Code Playgroud)
有没有办法,结合预处理器,Boost和C++ 11,实现这样的东西,而不需要外部生成工具?(我认为Boost.Preprocessor称之为"水平"和"垂直"重复.我需要以某种方式"转置"字段数据.)这里的关键特征是我永远不必重复任何信息,并且修改或添加一个字段只需要一次更改.
您可以使用 boost 的预处理器序列来做到这一点。
#define CREATE_MESSAGE(NAME, SEQ) ...
CREATE_MESSAGE(SomeMessage,
(int)(header)
(double)(temperature)
(char)(flag)
)
Run Code Online (Sandbox Code Playgroud)
您需要迭代每一对来生成定义。我没有任何方便的示例代码,但如果有趣的话我可能可以安排一些。
有一次,我有一个类似这样的生成器,它还生成了字段的所有序列化。我感觉有点太过分了。我觉得具体的定义和现场的声明性访问者更加直接。如果其他人必须在我之后维护代码,那就有点不那么神奇了。我不知道你的具体情况,刚实施后我还是有所保留。:)
再次查看 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;
}
Run Code Online (Sandbox Code Playgroud)
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];
}
};
Run Code Online (Sandbox Code Playgroud)