Alin Andersen
3148 字
16 分钟
C++20-P2_RTTR运行时反射

封面来源:Cpp_RTTR_Library
- 目标
- 构建一个解释器,用户可以从脚本中读取飞行过程中的实时参数,由解释器对脚本进行解析,并将计算得到的量输回到仿真程序中继续仿真.
- 实现后,改飞控就不需要重新编译程序了,极大提高了灵活度.
- 待实现功能
- 用户输入的变量字符串->具体的变量地址.
- 可行方案
- 自行维护一个变量名-地址映射表
- 💔变量分布在多个类中,维护表的任务量非常大.
- 使用运行时反射
- ✔现成的RTTR库可以实现这一功能.
- 自行维护一个变量名-地址映射表
运行时反射:在运行期对对象进行自省/修改的能力.
-
实现了反射,就可以依据一个运行期提供的字符串,对各类对象进行动态查询/调用了.
-
在序列化/属性查询等方面具有重要应用.
工作流程
RTTR库遵循“注册-使用”两步走原则.
- 注册
- 由于C++编译器不支持反射,反射所需的类型名称等属性信息必须手工添加.
- 在各类定义文件中额外添加rttr注册信息,令其能够正确获取属性信息;
- 使用
- 使用rttr提供的各类api,可获取已注册的属性信息,并进一步完成取值/设值/执行等操作.
关键步骤
注册
-
一般不在头文件直接注册,使用其对应的源文件.
-
在该源文件中包含<rttr/registration>头.
-
使用RTTR_REGISTRATION宏
RTTR_REGISTRATION宏
- 必须位于全局作用域
- 在main函数调用前自动执行.
// 0.简单注册示例.// 在f.h中声明.void f();
// 在f.cc中定义与注册.#include <rttr/registration>void f() { std::cout << "Hello World" << std::endl; } // 函数定义.
RTTR_REGISTRATION // 注册部分{ using namespace rttr; registration::method("f", &f);}
为方法(Method)注册
// 1.为全局方法注册.// 参考上方的"简单注册示例"一节即可.
// 2.为重载方法注册.// 使用select_overload<>()// 对32位MSVC,需要显式类型转换如static_cast<float(*)(float)>(&sin)RTTR_REGISTRATION{ using namespace rttr; registration::method("sin", select_overload<float(float)>(&sin)) .method("sin", select_overload<double(double)>(&sin));}
为属性注册
// 使用rttr::registration::property()与rttr::registration::property_readonly().// 其中A是指向对象的指针.template<typename A>registration rttr::registration::property( string_view name, A accessor );template<typename A>registration rttr::registration::property_readonly(string_view name, A accessor);template<typename A1, typename A2>registration rttr::registration::property( string_view name, A1 getter, A2 setter );
// 注册示例.#include <rttr/registration>using namespace rttr;static const double pi = 3.14259;static std::string global_text;void set_text(const std::string& text) { global_text = text; }const std::string& get_text() { return global_text; }RTTR_REGISTRATION{ using namespace rttr; registration::property_readonly("PI", &pi); registration::property("global_text", &get_text, &set_text); // 带setter与getter.}
为枚举值注册
template<typename Enum_Type>registration rttr::registration::enumeration( string_view name );
// 注册示例.#include <rttr/registration>using namespace rttr;enum class E_Alignment{ AlignLeft = 0x0001, AlignRight = 0x0002, AlignHCenter = 0x0004, AlignJustify = 0x0008};RTTR_REGISTRATION{ registration::enumeration<E_Alignment>("E_Alignment") ( value("AlignLeft", E_Alignment::AlignLeft), value("AlignRight", E_Alignment::AlignRight), value("AlignHCenter", E_Alignment::AlignHCenter), value("AlignJustify", E_Alignment::AlignJustify) );}
为类注册
// 1.简单示例.#include <rttr/type>struct test_class{ test_class(int value) : m_value(value) {} void print_value() const { std::cout << m_value; } int m_value; const int r_value; int get_set; void set_value(int x) { get_set = x; } int get_value() const { return get_set; } // 注意const是必须的. RTTR_ENABLE() private: // ... /* RTTR_REGISTRATION_FRIEND // 这行在需注册类内private对象时写. */};
// test_class.cpp#include <rttr/registration>RTTR_REGISTRATION{ using namespace rttr; registration::class_<test_class>("test_class") .constructor<int>() .method("print_value", &test_class::print_value) .property("value", &test_class::m_value) .property_readonly("rvalue", &&test_class::r_value) // 只读参数. .property("get_set", &test_class::get_value, &test_class::set_value); // 使用getter-setter.}
// 2.注册重载函数.struct Foo{ void f() {} void f(int) {} void f(int) const {}};RTTR_REGISTRATION{ using namespace rttr; registration::class_<Foo>("Foo") .method("f", select_overload<void(void)>(&Foo::f)) .method("f", select_overload<void(int)>(&Foo::f)) .method("f", select_const(&Foo::f)); // 注意const函数.}
// 3.注册构造函数,支持使用static函数构造.struct Foo{ Foo(); Foo(int, double); Foo(const std::string&); static Foo* create();};
RTTR_REGISTRATION{ using namespace rttr; registration::class_<Foo>("Foo") .constructor<>() .constructor<int,double>() .constructor<const std::string&>() .constructor(&Foo::create);}
// 4.设定访问可见性.using namespace rttr;struct Foo{public: Foo() {}protected: void func() {}private: int value; RTTR_REGISTRATION_FRIEND};RTTR_REGISTRATION{ registration::class_<Foo>("Foo") .constructor<>(registration::public_access) // 不必写,默认设置. .method("func", &Foo::func, registration::protected_access) .property("value", &Foo::value, registration::private_access);}
// 5.注册继承层次.// 使用RTTR_ENABLE宏,其所在public,private,protected部段不受限制.struct Base{ RTTR_ENABLE()};// 注册子类,注意在宏内添加了父类信息.struct Derived : Base, Other{ RTTR_ENABLE(Base, Other)};
使用
调用方法
// 1. rttr::type::invoke().// 使用函数名+一个vector参数组进行函数调用.variant return_value = type::invoke("pow", {9.0, 2.0}); // 计算9^2,得到81.if (return_value.is_valid() && return_value.is_type<double>()) // 检验结果有效性. std::cout << return_value.get_value<double>() << std::endl; // 获取值.
// 2.rttr::type::get_global_method().// 多次调用时比第一种方法更快,省去了多次查找的过程.method meth = type::get_global_method("pow"); // 获取轻量化的meth对象,其有效期直至main()结束.if (meth){ return_value = meth.invoke({}, 9.0, 3.0); // 在空对象上调用. if (return_value.is_valid() && return_value.is_type<double>()) std::cout << return_value.get_value<double>() << std::endl; // 729}
读写属性
// 两方法的优劣势类似于调用方法一节.// 1.使用type::set_property_value()与type::get_property_value().// 2.首先使用type::get_global_property(),再用property::set_value()与property::get_value().using namespace rttr;int main(){ // 方法1. variant value = type::get_property_value("PI"); // remark the capitalization of "PI" if (value && value.is_type<double>()) // 前半与value.is_valid()等效. std::cout << value.get_value<double>() << std::endl; // outputs: "3.14259" // 方法2. property prop = type::get_global_property("PI"); if (prop) { value = prop.get_value(instance()); if (value.is_valid() && value.is_type<double>()) std::cout << value.get_value<double>() << std::endl; // outputs: "3.14259" }}
调用枚举值
using namespace rttr;type enum_type = type::get_by_name("E_Alignment");if (enum_type && enum_type.is_enumeration()){ enumeration enum_align = enum_type.get_enumeration(); std::string name = enum_align.value_to_name(E_Alignment::AlignHCenter); std::cout << name; // prints "AlignHCenter" variant var = enum_align.name_to_value(name); E_Alignment value = var.get_value<E_Alignment>(); // stores value 'AlignHCenter'}
// 更简单的方式.variant var = E_Alignment::AlignHCenter;std::cout << var.to_int() << std::endl; // prints '4'std::cout << var.to_string() << std::endl; // prints 'AlignHCenter'
类型相关
// 1.创建与销毁.// 对于创建,与全局变量类似,也有两种方式.int main(){ using namespace rttr; // 选项1:直接使用type接口. type class_type = type::get_by_name("test_class"); if (class_type) { variant obj = class_type.create({23}); if (obj.get_type().is_pointer()) class_type.destroy(obj); } // 选项2:获得构造/析构函数对象. if (class_type) { constructor ctor = class_type.get_constructor({type::get<int>()}); variant obj = ctor.invoke(23); if (obj.get_type().is_pointer()) { destructor dtor = class_type.get_destructor(); dtor.invoke(obj); } }}
// 2.调用成员函数.int main(){ using namespace rttr; test_class obj(42); type class_type = type::get_by_name("test_class"); // 选项1. class_type.invoke("print_value", obj, {}); // print 42 // 选项2. // 对这种方法,第二步提供的obj也可以为指向该类对象的指针/智能指针. method print_meth = class_type.get_method("print_value"); print_meth.invoke(obj); // prints "42"}
// 3.设置成员变量.int main(){ using namespace rttr; test_class obj(0); type class_type = type::get_by_name("test_class"); // 选项1. bool success = class_type.set_property_value("value", obj, 50); std::cout << obj.m_value; // prints "50" // 选项2. property prop = class_type.get_property("value"); success = prop.set_value(obj, 24); std::cout << obj.m_value; // prints "24"}
常用类型与方法详解
rttr::variant
在返回属性/方法时被使用,允许类型擦除,允许在多种类型间透明互转.
存入过程默认存在一次拷贝,使用指针类型/std::reference_wrapper
using namespace rttr;variant var;var = 23;int x = var.to_int(); // variant转int.
var = "Hello World"; // var内容类型为std::stringint y = var.to_int(); // y == 0, 无效转换.
var = "42";std::cout << var.to_int(); // 有效转换,打印42.
int my_array[100];var = my_array; // 拷贝进入.auto& arr = var.get_value<int[100]>(); // 获得内容引用.
rttr::type
用于管理所有类型相关的操作.
// 1.获取rttr::type对象.// 无法直接创建,只能通过getter函数获取.rttr::type::get<T>()rttr::type::get<T>(T&& obj)// 以下为示例.#include <rttr/type>using namespace rttr;type my_int_type = type::get<int>(); // 静态获取.type my_bool_type = type::get(true); // 动态获取,等效于type::get<bool>().
// 2.继承特性.struct Base {};struct Derived : Base {};Derived d;Base& base = d;type::get<Derived>() == type::get(base) // yields to truetype::get<Base>() == type::get(base) // yields to false// REMARK when called with pointers:Base* base_ptr = &d;type::get<Derived>() == type::get(base_ptr); // yields to falsetype::get<Base*>() == type::get(base_ptr); // yields to true
// 3.对于cv约束的特性.// 所有顶层cv都将被移除.class D { ... };D d1;const D d2;type::get(d1) == type::get(d2); // yields truetype::get<D>() == type::get<const D>(); // yields truetype::get<D>() == type::get(d2); // yields truetype::get<D>() == type::get<const D&>(); // yields truetype::get<D>() == type::get<const D*>(); // yields false
// 4.rttr::type::get_by_name(string_view)// 通过名字查找类型.// 使用前,必须先调用一次type::get<T>(),不然RTTR不会在类型系统中注册要查找的类型.type::get_by_name("int") == type::get<int>(); // yields to truetype::get_by_name("bool") == type::get<int>(); // yields to falsetype::get_by_name("MyNameSpace::MyStruct") == type::get<MyNameSpace::MyStruct>(); // yields to true
// 5.容器支持.std::vector<rttr::type> type_list;std::map<rttr::type, std::string> mapping;std::unordered_map<rttr::type, std::string> type_names;
// 6.查询指令.// 6.1.常规查询.struct D { ... };type::get<D>().get_name(); // 依赖编译器实现,不可移植.type::get<D>().is_class(); // truetype::get<D>().is_pointer(); // falsetype::get<D*>().is_pointer(); // truetype::get<D>().is_array(); // falsetype::get<D[50]>().is_array(); // truetype::get<std::vector<D>>().is_array(); // truetype::get<D>().is_arithmetic(); // falsetype::get<D>().is_enumeration(); // false// 6.2.继承特性.struct Base { RTTR_ENABLE() };struct Derived : Base { RTTR_ENABLE(Base) };Derived d;std::vector<type> base_list = type::get(d).get_base_classes();for (auto& t : base_list) std::cout << t.get_name() << std::endl; // 'struct Base'type::get(d).is_derived_from<Base>(); // true
rttr_cast
// 类似于dynamic_cast,但无需RTTI,可跨动态库且更快.struct A { RTTR_ENABLE() };struct B : A { RTTR_ENABLE(A) };struct C : B { RTTR_ENABLE(B) };struct D : A, B { RTTR_ENABLE(A, B) };C c;D d;A* a = &c;A* a2 = &d;B* b = rttr_cast<B*>(a);B* b = rttr_cast<B*>(a2); // 对多继承也能成功.
论外
元数据(Metadata)与参数名称(Parameter Names)
可以为类、属性、方法、枚举以及构造函数添加运行时可访问的元数据.
// 1.存储元数据.// 使用metadata字段.#include <rttr/registration>int g_value;
enum class MetaData_Type{ SCRIPTABLE, GUI};RTTR_REGISTRATION{ using namespace rttr;
registration::property("value", &g_Value) // 为g_value添加元数据. ( metadata(MetaData_Type::SCRIPTABLE, false), metadata("Description", "This is a value.") );}
// 2.读取元数据.int main(){ using namespace rttr; property prop = type::get_global_property("value"); variant value = prop.get_metadata(MetaData_Type::SCRIPTABLE); std::cout << value.get_value<bool>(); // prints "false"
value = prop.get_metadata("Description"); std::cout << value.get_value<std::string>(); // prints "This is a value."}
// 3.特殊元数据-参数名.// 存入parameter_names字段.using namespace rttr;void set_window_geometry(const char* name, int w, int h) {...}RTTR_REGISTRATION{ registration::method("set_window_geometry", &set_window_geometry) ( parameter_names("window name", "width", "height") );}// 读取parameter_names字段.int main(){ method meth = type::get_global_method("set_window_geometry"); std::vector<parameter_info> param_list = meth.get_parameter_infos(); for (const auto& info : param_list) { // print all names of the parameter types and its position in the paramter list std::cout << " name: '" << info.get_type().get_name() << "'\n" << "index: " << info.get_index() << std::endl; }}
默认参数
不常用,因为会导致逻辑部分与用户交互间多出一层默认值配置,增加复杂性.
// 1.配置默认参数.using namespace rttr;void my_function(int a, bool b, const std::string& text, const int* ptr);RTTR_REGISTRATION{ registration::method("my_function", &my_function) ( // 为b, text与ptr配置默认值. // 参数表末尾与形参末尾对应,前序参数依次往前推. default_arguments(true, std::string("default text"), nullptr) );}
// 2.使用默认参数.int main(){ using namespace rttr; method meth = type::get_global_method("my_function"); // 以下等效于meth.invoke(instance(), 23, true, std::string("default text"), nullptr); variant var = meth.invoke(instance(), 23); std::cout << var.is_valid(); // prints 'true' // 前三个参数已给定,ptr取默认值nullptr. var = meth.invoke(instance(), 23, true, std::string("text")); std::cout << var.is_valid(); // prints 'true'}
构造策略
// 0.对构造函数,属性与方法,有不同的构造策略.// 构造函数:as_std_shared_ptr(默认), as_object, as_raw_ptr.// 属性: bind_as_ptr, as_reference_wrapper.// 方法:discard_return, return_ref_as_ptr.// 分别位于policy::ctor, policy::prop, policy::meth下.// 1. 构造函数.// 1.1.按对象构造.using namespace rttr;struct Foo{};RTTR_REGISTRATION{ registration::class_<Foo>("Foo") .constructor<>() ( policy::ctor::as_object );}int main(){ variant var = type::get<Foo>().create(); // 创建具有局部生命周期的裸对象. std::cout << var.is_type<Foo>(); return 0;}// 1.2.按shared_ptr构造.using namespace rttr;struct Foo{};RTTR_REGISTRATION{ registration::class_<Foo>("Foo") .constructor<>() ( policy::ctor::as_std_shared_ptr );}int main(){ variant var = type::get<Foo>().create(); std::cout << var.is_type<std::shared_ptr<Foo>>(); // prints "true" return 0;}// 1.3.按裸指针构造.// 注意使用destroy()以调用析构函数.using namespace rttr;struct Foo{};RTTR_REGISTRATION{ registration::class_<Foo>("Foo") .constructor<>() ( policy::ctor::as_raw_ptr );}int main(){ variant var = type::get<Foo>().create(); std::cout << var.is_type<Foo*>(); // prints "true" var.get_type().destroy(var); // free's the memory with 'delete' std::cout << var.is_valid(); // prints "false" return 0;}
// 2.属性.// 2.1. 绑定为指针.// 可以避免大数组的拷贝操作.struct Foo{ std::vector<int> vec;};RTTR_REGISTRATION{ using namespace rttr; registration::class_<Foo>("Foo") .property("vec", &Foo::vec) ( policy::prop::bind_as_ptr )}int main(){ Foo obj; property prop = type::get<Foo>().get_property("vec"); variant var = prop.get_value(obj); std::cout << var.is_type<std::vector<int>*>(); // prints "true" prop.set_value(obj, var);}// 2.2.按引用对象.struct Foo{ std::vector<int> vec;};RTTR_REGISTRATION{ using namespace rttr; registration::class_<Foo>("Foo") .property("vec", &Foo::vec) ( policy::prop::as_reference_wrapper )}int main(){ Foo obj; property prop = type::get<Foo>().get_property("vec"); variant var = prop.get_value(obj); std::cout << var.is_type< std::reference_wrapper<std::vector<int>> >(); // prints "true". prop.set_value(obj, var);}
// 3.属性.// 3.1.丢弃输出.struct Foo{ int calculate_func() { return 42; } // 这里返回int.};RTTR_REGISTRATION{ using namespace rttr; registration::class_<Foo>("Foo") .method("get_value", &Foo::calculate_func) ( policy::meth::discard_return );}int main(){ Foo obj; method meth = type::get<Foo>().get_method("calculate_func"); variant var = meth.invoke(obj); std::cout << var.is_type<void>(); // 实际这里没有返回, 显示为true.}// 3.2.以指针形式返回.// 同样可以避免大数组拷贝.struct Foo{ std::string& get_text() { static std::string text; return text; }};RTTR_REGISTRATION{ using namespace rttr; registration::class_<Foo>("Foo") .method("get_text", &Foo::get_text) ( policy::meth::return_ref_as_ptr );}int main(){ Foo obj; method meth = type::get<Foo>().get_method("get_text"); variant var = meth.invoke(obj); std::cout << var.is_type<std::string*>(); // prints "true"}
动态库中的注册
// 使用gcc时,务必使用-fno-gnu-unique.// 编译形成MyPlugin.dll#include <rttr/registration>struct MyPluginClass{ MyPluginClass(){} void perform_calculation() { value += 12; } void perform_calculation(int new_value) { value += new_value; } int value = 0;};RTTR_PLUGIN_REGISTRATION // 注意:使用了不同的宏!{ rttr::registration::class_<MyPluginClass>("MyPluginClass") .constructor<>() .property("value", &MyPluginClass::value) .method("perform_calculation", rttr::select_overload<void(void)>(&MyPluginClass::perform_calculation)) .method("perform_calculation", rttr::select_overload<void(int)>(&MyPluginClass::perform_calculation)) ;}
// 使用库.#include <rttr/type>int main(int argc, char** argv){ using namespace rttr; // 后缀名将根据平台自动补全. library lib("MyPlugin"); if (!lib.load()) { std::cerr << lib.get_error_string() << std::endl; return -1; } // 打印库内部的所有类. for (auto t : lib.get_types()) { if (t.is_class() && !t.is_wrapper()) std::cout << t.get_name() << std::endl; } // we cannot use the actual type, to get the type information, // thus we use string to retrieve it auto t = type::get_by_name("MyPluginClass"); // iterate over all methods of the class for (auto meth : t.get_methods()) { std::cout << meth.get_signature() << std::endl; } // work with the new type auto var = t.create(); t.invoke("perform_calculation", var, {}); std::cout << t.get_property_value("value", var).to_int() << std::endl; // prints "12" return 0;}// 输出:// MyPluginClass// perform_calculation( )// perform_calculation( int )// 12
C++20-P2_RTTR运行时反射
https://www.lithium-hydroxide.space/posts/250829_cpp_rttr/