如何在没有 x-macros 的情况下在 C 中获得类似反射的功能
How to get reflection-like functionality in C, without x-macros
与this question on Software Engineering about easily serializing various struct contents on demand相关,我发现一篇文章使用x-macros创建"out of the box"结构序列化所需的结构元数据。我也看到了 "smart enums" 的类似技术,但它归结为相同的原则,即获取枚举的字符串表示形式,或通过名称获取结构的字段值,或类似的东西。
然而,Stack Overflow 上经验丰富的 C 程序员指出,x-macros 应该避免使用,因为 "last resort":
- How to access member of struct dynamically in C?
我可能会找到更多相关主题,但不幸的是我没有将它们加入书签所以这只是一些 Google-fu。
也许正确答案类似于Protocol Buffers?但是,为什么用不同的语言创建结构定义(.proto
定义)然后 运行 生成 C 文件的构建步骤比使用内置预处理器更可取呢?问题是这些技术仍然不允许我通过名称检索单个结构,我必须在两个项目之间共享相同的定义并保持它们同步。
那么问题是:如果 x-macros 是 "last resort",我的问题的解决方法(当从不同设备请求时轻松序列化各种内部数据)将是 "first resort",或任何其他方法在求助于宏地狱之前?
"first resort" 通常是以下之一:
将您的所有数据分组在由 arrays/structs 组成的 table 中,最好是只读数据,如第一个链接示例中所示。 table 索引用作将数据保持在一起的搜索键("primary key" 使用 RDBMS 术语)。这是快速且可读的,但在维护期间必须小心。
根据一些 OO 设计对数据进行分组。可以使用不透明指针和函数指针来实现私有封装和多态。如果使用得当,这可以提供最先进的程序设计。但与此同时,编写起来可能有些繁琐。如果您不能使用动态内存分配(嵌入式系统),那么您必须为每个 class 发明一个内存池。最适合更复杂的 "ADT" 类容器和 API 设计。
话虽如此,只要您不假设每个 reader 都熟悉 X-macros,就可以接受 table。因此,我会留下一些关于宏列表如何工作、使用时如何扩展以及应如何维护的评论。
从链接的代码示例中,#define X(dir) {dir, #dir}
行也许应该像这样注释得更恰当:
/*
Create a temporary X-macro that expands the DIRECTION_LIST, to form
an array initialization list. The format will be:
{north, "north"},
{south, "south"},
...
*/
#define X(dir) {dir, #dir}
DIRECTION_LIST
#undef X
借助 Boost 的一些预处理器魔法,我们可以使宏能够生成可反射的枚举。
我设法构建了下面提供的简单概念验证实现。
首先,用法。正在关注:
ReflEnum(MyEnum,
(first)
(second , 42)
(third)
)
扩展为:
enum MyEnum
{
first,
second = 42,
third,
};
const char *EnumToString_MyEnum(enum MyEnum param)
{
switch (param)
{
case first:
return "first";
case second:
return "second";
case third:
return "third";
default:
return "<invalid>";
}
}
因此一个完整的程序可能如下所示:
#include <stdio.h>
/*
* Following is generated by the below ReflEnum():
* enum MyEnum {first, second = 42, third};
* const char *EnumToString_MyEnum(enum MyEnum value) {}
*/
ReflEnum(MyEnum,
(first)
(second , 42)
(third)
)
int main()
{
enum MyEnum foo = second;
puts(EnumToString_MyEnum(foo)); // -> "second"
puts(EnumToString_MyEnum(43)); // -> "third"
puts(EnumToString_MyEnum(9001)); // -> "<invalid>"
}
这是实现本身。
它由两部分组成。代码本身和预处理器魔术头文件无耻地从 Boost 中窃取。
代码:
#define ReflEnum_impl_Item(...) PPUTILS_VA_CALL(ReflEnum_impl_Item_, __VA_ARGS__)(__VA_ARGS__)
#define ReflEnum_impl_Item_1(name) name,
#define ReflEnum_impl_Item_2(name, value) name = value,
#define ReflEnum_impl_Case(...) case PPUTILS_VA_FIRST(__VA_ARGS__): return PPUTILS_STR(PPUTILS_VA_FIRST(__VA_ARGS__));
#define ReflEnum(name, seq) \
enum name {PPUTILS_SEQ_APPLY(seq, ReflEnum_impl_Item)}; \
const char *EnumToString_##name(enum name param) \
{ \
switch (param) \
{ \
PPUTILS_SEQ_APPLY(seq, ReflEnum_impl_Case) \
default: return "<invalid>"; \
} \
}
扩展代码以支持字符串-> 枚举转换应该太难;如果您不确定,请在评论中提问。
魔法:
请注意,预处理器魔法必须由脚本生成,生成时必须选择最大枚举大小。生成很容易,留给 reader.
作为练习。
Boost 默认大小为 64
,下面的代码是为大小 4
生成的。
#define PPUTILS_E(...) __VA_ARGS__
#define PPUTILS_VA_FIRST(...) PPUTILS_VA_FIRST_IMPL_(__VA_ARGS__,)
#define PPUTILS_VA_FIRST_IMPL_(x, ...) x
#define PPUTILS_PARENS(...) (__VA_ARGS__)
#define PPUTILS_DEL_PARENS(...) PPUTILS_E __VA_ARGS__
#define PPUTILS_CC(a, b) PPUTILS_CC_IMPL_(a,b)
#define PPUTILS_CC_IMPL_(a, b) a##b
#define PPUTILS_CALL(macro, ...) macro(__VA_ARGS__)
#define PPUTILS_VA_SIZE(...) PPUTILS_VA_SIZE_IMPL_(__VA_ARGS__,4,3,2,1,0)
#define PPUTILS_VA_SIZE_IMPL_(i1,i2,i3,i4,size,...) size
#define PPUTILS_STR(...) PPUTILS_STR_IMPL_(__VA_ARGS__)
#define PPUTILS_STR_IMPL_(...) #__VA_ARGS__
#define PPUTILS_VA_CALL(name, ...) PPUTILS_CC(name, PPUTILS_VA_SIZE(__VA_ARGS__))
#define PPUTILS_SEQ_CALL(name, seq) PPUTILS_CC(name, PPUTILS_SEQ_SIZE(seq))
#define PPUTILS_SEQ_DEL_FIRST(seq) PPUTILS_SEQ_DEL_FIRST_IMPL_ seq
#define PPUTILS_SEQ_DEL_FIRST_IMPL_(...)
#define PPUTILS_SEQ_FIRST(seq) PPUTILS_DEL_PARENS(PPUTILS_VA_FIRST(PPUTILS_SEQ_FIRST_IMPL_ seq,))
#define PPUTILS_SEQ_FIRST_IMPL_(...) (__VA_ARGS__),
#define PPUTILS_SEQ_SIZE(seq) PPUTILS_CC(PPUTILS_SEQ_SIZE_0 seq, _VAL)
#define PPUTILS_SEQ_SIZE_0(...) PPUTILS_SEQ_SIZE_1
#define PPUTILS_SEQ_SIZE_1(...) PPUTILS_SEQ_SIZE_2
#define PPUTILS_SEQ_SIZE_2(...) PPUTILS_SEQ_SIZE_3
#define PPUTILS_SEQ_SIZE_3(...) PPUTILS_SEQ_SIZE_4
#define PPUTILS_SEQ_SIZE_4(...) PPUTILS_SEQ_SIZE_5
// Generate PPUTILS_SEQ_SIZE_i
#define PPUTILS_SEQ_SIZE_0_VAL 0
#define PPUTILS_SEQ_SIZE_1_VAL 1
#define PPUTILS_SEQ_SIZE_2_VAL 2
#define PPUTILS_SEQ_SIZE_3_VAL 3
#define PPUTILS_SEQ_SIZE_4_VAL 4
// Generate PPUTILS_SEQ_SIZE_i_VAL
#define PPUTILS_SEQ_APPLY(seq, macro) PPUTILS_SEQ_CALL(PPUTILS_SEQ_APPLY_, seq)(macro, seq)
#define PPUTILS_SEQ_APPLY_0(macro, seq)
#define PPUTILS_SEQ_APPLY_1(macro, seq) PPUTILS_CALL(macro, PPUTILS_SEQ_FIRST(seq))
#define PPUTILS_SEQ_APPLY_2(macro, seq) PPUTILS_CALL(macro, PPUTILS_SEQ_FIRST(seq)) PPUTILS_SEQ_CALL(PPUTILS_SEQ_APPLY_, PPUTILS_SEQ_DEL_FIRST(seq))(macro, PPUTILS_SEQ_DEL_FIRST(seq))
#define PPUTILS_SEQ_APPLY_3(macro, seq) PPUTILS_CALL(macro, PPUTILS_SEQ_FIRST(seq)) PPUTILS_SEQ_CALL(PPUTILS_SEQ_APPLY_, PPUTILS_SEQ_DEL_FIRST(seq))(macro, PPUTILS_SEQ_DEL_FIRST(seq))
#define PPUTILS_SEQ_APPLY_4(macro, seq) PPUTILS_CALL(macro, PPUTILS_SEQ_FIRST(seq)) PPUTILS_SEQ_CALL(PPUTILS_SEQ_APPLY_, PPUTILS_SEQ_DEL_FIRST(seq))(macro, PPUTILS_SEQ_DEL_FIRST(seq))
// Generate PPUTILS_SEQ_APPLY_i
与this question on Software Engineering about easily serializing various struct contents on demand相关,我发现一篇文章使用x-macros创建"out of the box"结构序列化所需的结构元数据。我也看到了 "smart enums" 的类似技术,但它归结为相同的原则,即获取枚举的字符串表示形式,或通过名称获取结构的字段值,或类似的东西。
然而,Stack Overflow 上经验丰富的 C 程序员指出,x-macros 应该避免使用,因为 "last resort":
- How to access member of struct dynamically in C?
我可能会找到更多相关主题,但不幸的是我没有将它们加入书签所以这只是一些 Google-fu。
也许正确答案类似于Protocol Buffers?但是,为什么用不同的语言创建结构定义(.proto
定义)然后 运行 生成 C 文件的构建步骤比使用内置预处理器更可取呢?问题是这些技术仍然不允许我通过名称检索单个结构,我必须在两个项目之间共享相同的定义并保持它们同步。
那么问题是:如果 x-macros 是 "last resort",我的问题的解决方法(当从不同设备请求时轻松序列化各种内部数据)将是 "first resort",或任何其他方法在求助于宏地狱之前?
"first resort" 通常是以下之一:
将您的所有数据分组在由 arrays/structs 组成的 table 中,最好是只读数据,如第一个链接示例中所示。 table 索引用作将数据保持在一起的搜索键("primary key" 使用 RDBMS 术语)。这是快速且可读的,但在维护期间必须小心。
根据一些 OO 设计对数据进行分组。可以使用不透明指针和函数指针来实现私有封装和多态。如果使用得当,这可以提供最先进的程序设计。但与此同时,编写起来可能有些繁琐。如果您不能使用动态内存分配(嵌入式系统),那么您必须为每个 class 发明一个内存池。最适合更复杂的 "ADT" 类容器和 API 设计。
话虽如此,只要您不假设每个 reader 都熟悉 X-macros,就可以接受 table。因此,我会留下一些关于宏列表如何工作、使用时如何扩展以及应如何维护的评论。
从链接的代码示例中,#define X(dir) {dir, #dir}
行也许应该像这样注释得更恰当:
/*
Create a temporary X-macro that expands the DIRECTION_LIST, to form
an array initialization list. The format will be:
{north, "north"},
{south, "south"},
...
*/
#define X(dir) {dir, #dir}
DIRECTION_LIST
#undef X
借助 Boost 的一些预处理器魔法,我们可以使宏能够生成可反射的枚举。
我设法构建了下面提供的简单概念验证实现。
首先,用法。正在关注:
ReflEnum(MyEnum,
(first)
(second , 42)
(third)
)
扩展为:
enum MyEnum
{
first,
second = 42,
third,
};
const char *EnumToString_MyEnum(enum MyEnum param)
{
switch (param)
{
case first:
return "first";
case second:
return "second";
case third:
return "third";
default:
return "<invalid>";
}
}
因此一个完整的程序可能如下所示:
#include <stdio.h>
/*
* Following is generated by the below ReflEnum():
* enum MyEnum {first, second = 42, third};
* const char *EnumToString_MyEnum(enum MyEnum value) {}
*/
ReflEnum(MyEnum,
(first)
(second , 42)
(third)
)
int main()
{
enum MyEnum foo = second;
puts(EnumToString_MyEnum(foo)); // -> "second"
puts(EnumToString_MyEnum(43)); // -> "third"
puts(EnumToString_MyEnum(9001)); // -> "<invalid>"
}
这是实现本身。
它由两部分组成。代码本身和预处理器魔术头文件无耻地从 Boost 中窃取。
代码:
#define ReflEnum_impl_Item(...) PPUTILS_VA_CALL(ReflEnum_impl_Item_, __VA_ARGS__)(__VA_ARGS__)
#define ReflEnum_impl_Item_1(name) name,
#define ReflEnum_impl_Item_2(name, value) name = value,
#define ReflEnum_impl_Case(...) case PPUTILS_VA_FIRST(__VA_ARGS__): return PPUTILS_STR(PPUTILS_VA_FIRST(__VA_ARGS__));
#define ReflEnum(name, seq) \
enum name {PPUTILS_SEQ_APPLY(seq, ReflEnum_impl_Item)}; \
const char *EnumToString_##name(enum name param) \
{ \
switch (param) \
{ \
PPUTILS_SEQ_APPLY(seq, ReflEnum_impl_Case) \
default: return "<invalid>"; \
} \
}
扩展代码以支持字符串-> 枚举转换应该太难;如果您不确定,请在评论中提问。
魔法:
请注意,预处理器魔法必须由脚本生成,生成时必须选择最大枚举大小。生成很容易,留给 reader.
作为练习。Boost 默认大小为 64
,下面的代码是为大小 4
生成的。
#define PPUTILS_E(...) __VA_ARGS__
#define PPUTILS_VA_FIRST(...) PPUTILS_VA_FIRST_IMPL_(__VA_ARGS__,)
#define PPUTILS_VA_FIRST_IMPL_(x, ...) x
#define PPUTILS_PARENS(...) (__VA_ARGS__)
#define PPUTILS_DEL_PARENS(...) PPUTILS_E __VA_ARGS__
#define PPUTILS_CC(a, b) PPUTILS_CC_IMPL_(a,b)
#define PPUTILS_CC_IMPL_(a, b) a##b
#define PPUTILS_CALL(macro, ...) macro(__VA_ARGS__)
#define PPUTILS_VA_SIZE(...) PPUTILS_VA_SIZE_IMPL_(__VA_ARGS__,4,3,2,1,0)
#define PPUTILS_VA_SIZE_IMPL_(i1,i2,i3,i4,size,...) size
#define PPUTILS_STR(...) PPUTILS_STR_IMPL_(__VA_ARGS__)
#define PPUTILS_STR_IMPL_(...) #__VA_ARGS__
#define PPUTILS_VA_CALL(name, ...) PPUTILS_CC(name, PPUTILS_VA_SIZE(__VA_ARGS__))
#define PPUTILS_SEQ_CALL(name, seq) PPUTILS_CC(name, PPUTILS_SEQ_SIZE(seq))
#define PPUTILS_SEQ_DEL_FIRST(seq) PPUTILS_SEQ_DEL_FIRST_IMPL_ seq
#define PPUTILS_SEQ_DEL_FIRST_IMPL_(...)
#define PPUTILS_SEQ_FIRST(seq) PPUTILS_DEL_PARENS(PPUTILS_VA_FIRST(PPUTILS_SEQ_FIRST_IMPL_ seq,))
#define PPUTILS_SEQ_FIRST_IMPL_(...) (__VA_ARGS__),
#define PPUTILS_SEQ_SIZE(seq) PPUTILS_CC(PPUTILS_SEQ_SIZE_0 seq, _VAL)
#define PPUTILS_SEQ_SIZE_0(...) PPUTILS_SEQ_SIZE_1
#define PPUTILS_SEQ_SIZE_1(...) PPUTILS_SEQ_SIZE_2
#define PPUTILS_SEQ_SIZE_2(...) PPUTILS_SEQ_SIZE_3
#define PPUTILS_SEQ_SIZE_3(...) PPUTILS_SEQ_SIZE_4
#define PPUTILS_SEQ_SIZE_4(...) PPUTILS_SEQ_SIZE_5
// Generate PPUTILS_SEQ_SIZE_i
#define PPUTILS_SEQ_SIZE_0_VAL 0
#define PPUTILS_SEQ_SIZE_1_VAL 1
#define PPUTILS_SEQ_SIZE_2_VAL 2
#define PPUTILS_SEQ_SIZE_3_VAL 3
#define PPUTILS_SEQ_SIZE_4_VAL 4
// Generate PPUTILS_SEQ_SIZE_i_VAL
#define PPUTILS_SEQ_APPLY(seq, macro) PPUTILS_SEQ_CALL(PPUTILS_SEQ_APPLY_, seq)(macro, seq)
#define PPUTILS_SEQ_APPLY_0(macro, seq)
#define PPUTILS_SEQ_APPLY_1(macro, seq) PPUTILS_CALL(macro, PPUTILS_SEQ_FIRST(seq))
#define PPUTILS_SEQ_APPLY_2(macro, seq) PPUTILS_CALL(macro, PPUTILS_SEQ_FIRST(seq)) PPUTILS_SEQ_CALL(PPUTILS_SEQ_APPLY_, PPUTILS_SEQ_DEL_FIRST(seq))(macro, PPUTILS_SEQ_DEL_FIRST(seq))
#define PPUTILS_SEQ_APPLY_3(macro, seq) PPUTILS_CALL(macro, PPUTILS_SEQ_FIRST(seq)) PPUTILS_SEQ_CALL(PPUTILS_SEQ_APPLY_, PPUTILS_SEQ_DEL_FIRST(seq))(macro, PPUTILS_SEQ_DEL_FIRST(seq))
#define PPUTILS_SEQ_APPLY_4(macro, seq) PPUTILS_CALL(macro, PPUTILS_SEQ_FIRST(seq)) PPUTILS_SEQ_CALL(PPUTILS_SEQ_APPLY_, PPUTILS_SEQ_DEL_FIRST(seq))(macro, PPUTILS_SEQ_DEL_FIRST(seq))
// Generate PPUTILS_SEQ_APPLY_i