c++反射----现有解决方案
根据我们上一篇中的介绍,拦在我们实现行为树运行时路上有如下几个问题:枚举与字符串之间的相互映射
自定义类型的序列化和反序列化
成员函数的按名调用
函数接口的注释导出
这几个问题,难度逐次递进,下面我们来依次来分析一下这几个问题的现有解决方案。
枚举类型的反射
手动进行字符串映射
枚举类型的反射需求不仅仅在行为树的运行时需要,行为树的编辑器和调试器也是需要的。下面就是我们现有的所有节点的类型枚举:
enum class node_type
{
invalid = 0,
root,
negative,
sequence,
always_seq,
random_seq,
if_else,
while_loop,
wait_event,
reset,
sub_tree,
parallel,
action,
always_true,
select,
probility
};
对应的编辑器里面插入节点的时候需要提供所有可选节点类型的列表:
在代码里是通过人工遍历枚举的字符串形式来处理的:
std::vector<:string> node_types;
node_types.push_back("negative");
node_types.push_back("sequence");
node_types.push_back("always_seq");
node_types.push_back("random_seq");
node_types.push_back("select");
node_types.push_back("probility");
node_types.push_back("if_else");
node_types.push_back("while_loop");
node_types.push_back("wait_event");
node_types.push_back("reset");
node_types.push_back("sub_tree");
node_types.push_back("parallel");
node_types.push_back("action");
node_types.push_back("always_true");
然后行为树运行时里面又有从节点类型的字符串形式构造对象的方法:
node_type str_to_node_type(const std::string& cur_str)
{
static unordered_map<:string node_type> str_to_types = {
{"root", node_type::root},
{"always_seq", node_type::always_seq},
{"if_else", node_type::if_else},
{"reset", node_type::reset},
{"while_loop", node_type::while_loop}
// and more }
auto cur_iter = str_to_types.find(cur_str);
if(cur_iter == str_to_types.end())
{
return node_type::invalid;
}
else
{
return cur_iter->second;
}
}
这样的转换代码不仅仅是这两个地方有,每次人工填写的时候都怕自己写错了映射。
使用宏来生成枚举的反射
说起自动化生成模式化代码,很多人的第一反应就是宏,而宏的确能够在一定程度上解决这样的枚举发射的问题。下面就是一个
的示例:
#define DECLARE_ENUM_WITH_TYPE(E, T, ...) enum class E : T
{
__VA_ARGS__
};
std::map E##MapName(generateEnumMap(#__VA_ARGS__));
std::ostream &operator<
{
os << E##MapName[static_cast(enumTmp)];
return os;
}
size_t operator*(E enumTmp) { (void) enumTmp; return E##MapName.size(); }
std::string operator~(E enumTmp) { return E##MapName[static_cast(enumTmp)]; }
std::string operator+(std::string &&str, E enumTmp) { return str + E##MapName[static_cast(enumTmp)]; }
std::string operator+(E enumTmp, std::string &&str) { return E##MapName[static_cast(enumTmp)] + str; }
std::string &operator+=(std::string &str, E enumTmp)
{
str += E##MapName[static_cast(enumTmp)];
return str;
}
E operator++(E &enumTmp)
{
auto iter = E##MapName.find(static_cast(enumTmp)); // some content deleted enumTmp = static_cast(iter->first);
return enumTmp;
}
bool valid##E(T value) { return (E##MapName.find(value) != E##MapName.end()); }
#define DECLARE_ENUM(E, ...) DECLARE_ENUM_WITH_TYPE(E, int32_t, __VA_ARGS__) template
std::map generateEnumMap(std::string strMap)
{
STRING_REMOVE_CHAR(strMap, ' ');
STRING_REMOVE_CHAR(strMap, '(');
std::vector<:string> enumTokens(splitString(strMap));
std::map retMap;
T inxMap;
inxMap = 0;
for (auto iter = enumTokens.begin(); iter != enumTokens.end(); ++iter)
{
// Token: [EnumName | EnumName=EnumValue] std::string enumName;
T enumValue;
if (iter->find('=') == std::string::npos)
{
enumName = *iter;
}
else
{
std::vector<:string> enumNameValue(splitString(*iter, '='));
enumName = enumNameValue[0];
//inxMap = static_cast(enumNameValue[1]); inxMap = static_cast(std::stoll(enumNameValue[1], 0, 0));
}
retMap[inxMap++] = enumName;
}
return retMap;
}
上面的代码里面就有两个特别巨大的宏,一眼望去好多字符串拼接,咱也不懂什么意思,咱也不敢问。反正用法是这样的:
DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);
int main(void) {
TestEnumClass first, second;
first = TestEnumClass::FOUR;
second = TestEnumClass::TWO;
std::cout << first << "(" << static_cast(first) << ")" << std::endl; // FOUR(4)
std::string strOne;
strOne = ~first;
std::cout << strOne << std::endl; // FOUR
std::string strTwo;
strTwo = ("Enum-" + second) + (TestEnumClass::THREE + "-test");
std::cout << strTwo << std::endl; // Enum-TWOTHREE-test
std::string strThree("TestEnumClass: ");
strThree += second;
std::cout << strThree << std::endl; // TestEnumClass: TWO std::cout << "Enum count=" << *first << std::endl;
}
用宏来实现枚举发射最大的问题是他把我们所期望的枚举类型转变成了非枚举类型,同时实现也很扭曲令人生畏。
利用magic_enum进行字符串映射
在进一步搜索中发现,github上有一个magic_enum(https://github.com/Neargye/magic_enum)的库可以非侵入的实现枚举的遍历