在 C/C++ 之中默认提供的 “RTTI” 运行时类型信息反射机制过于脆弱,基本就等于没什么卵用,但在 C/C++ 11 之前模板元编程不是那么强大的时候,还是有那么一点点作用。
我们通常利用RTTI反射T的类型或其泛型类型(即T的T)(字符串,缩写形式)根据类型的判断来调用对应的特例实现,但是用的不多,在C/C++ 11及以上版本之中,实战的用处基本就等于没有了。
但是我们希望实现一个没那么强大的类型反射系统,可以获取、设置属性、调用方法的话,其实也是有一些办法的。
本文简单的例子,不代表需要去构造完整的框架链,只是让大家知道,C/C++ 应该怎么做反射框架,就像有些博主教 C/C++ 怎么做GC自动垃圾回收系统是一样的。
C/C++ 什么都可以办到,这么强大的中高级语言编程工具,不能只是写点 Simple 无涵养的业务逻辑代码,喜欢做搬砖的杂活,Java、Go 语言会是很好的一个选择。
本文将实现一个简单反射读取、修改结构体的成员字段的示例程式:
玩家们可以不选择我们这种实现形式,方法有很多,比如:nlohmann / json 实现RTTI反射框架的形式,也挺有一些意思的。
调用本文做的反射读写字段小例子:
int main(int argc, const char* argv[]) noexcept
{Foo foo;foo.x = 200;foo.y = 100;std::unordered_map<std::string, int>& fields = Foo::__RTTI__::c().__rtti_field;RTTI_SetField(fields, &foo, "x", 300);RTTI_SetField(fields, &foo, "y", 400);std::cout << "x: " << RTTI_GetField(fields, &foo, "x", 0) << " y: " << RTTI_GetField(fields, &foo, "y", 0) << std::endl;
}
实现 C/C++ 结构类成员字段反射:
#define RTTI_FILED(var) __rtti_field[#var] = offset_of(_Ty, var);
#define RTTI_S(T) \
struct __RTTI__ { \typedef T _Ty; \\std::unordered_map<std::string, int> __rtti_field; \\static __RTTI__& c() { \static __RTTI__ r; \return r; \} \\__RTTI__() { #define RTTI_E \} \
};struct Foo {int x;int y;RTTI_S(Foo);RTTI_FILED(x);RTTI_FILED(y);RTTI_E;
};template <typename T, typename TValue>
bool RTTI_SetField(std::unordered_map<std::string, int>& fields, T* obj, const std::string& method, TValue value) {auto tail = fields.find(method);auto endl = fields.end();if (tail == endl) {return false;}*(TValue*)((char*)obj + tail->second) = value;return true;
}template <typename T, typename TValue>
TValue RTTI_GetField(std::unordered_map<std::string, int>& fields, T* obj, const std::string& method, TValue deafult_value) {auto tail = fields.find(method);auto endl = fields.end();if (tail == endl) {return deafult_value;}return *(TValue*)((char*)obj + tail->second);
}
宏函数:(依赖)
查看本人该篇博文;
C/C++ 11 offsetof、container_of 宏函数的实现(无限制编译器)_c++ 11 container_of-CSDN博客
我们可以利用宏编程,在每个侵入的结构体类之中构造其成员(字段、函数)的反射信息,这个 C/C++ 框架做起来并不困难。
或者把存储结构体元数据的信息放在全局存储也可以,通过模板泛型类型T来捕获元数据信息就可以。
对于字段类设置值的话,有两种办法:
1、向本文这样计算字段在内存之中的偏移量
2、利用宏编程来完成 get、set 属性访问器的实现
函数调用这个也不困难的,
C++ 11 提供的可变参数模板,自己调用转发就好了,就是需要注意:返回值为 void 类型还是有值类型的。
最简单的做法是分一个 Call_Void 函数出来,一个 Call 带返回值的函数出来,当然 C/C++ 相当专业的做法显然是模板元编程咯,在 C/C++ 17 上面更容易处理,在 C/C++ 11 ~ 14 上面就需要注意下这些小问题。
都差不多,效率都挺OK,C/C++ 之中自己设计并且构造RTTI系统,只要不是SX玩意,乱整一通,基本上RTTI反射执行效率都是快到飞起滴。
当然有一些想当秀技术的也行,我之前看过一个开源项目,那个只有一个宏,把类型、字段、成员都在这个可变参数宏之中传递进去,然后大量通过 C/C++ 编译器模板元编程来处理。
项目的作者 C/C++ 水平相当秀儿,但大家在自己做 RTTI C/C++ 反射框架的时候大可不必这么秀技术水平,因为模板元编程虽好,但是过于多会导致编译器占用极大的设备内存,编译速度慢到飞起,能一定稳定编译过算运气好。
编译器内存OOM导致崩溃的问题,相信不少搞 C/C++ 的童靴应该都有遇到过,当模板过于复杂的时候,编译器编译一个CPP展开代码过多就有可能导致编译直接OOM炸掉故而编译失败。