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

封面来源:Cpp_RTTR_Library

  • 目标
    • 构建一个解释器,用户可以从脚本中读取飞行过程中的实时参数,由解释器对脚本进行解析,并将计算得到的量输回到仿真程序中继续仿真.
    • 实现后,改飞控就不需要重新编译程序了,极大提高了灵活度.
  • 待实现功能
    • 用户输入的变量字符串->具体的变量地址.
  • 可行方案
    1. 自行维护一个变量名-地址映射表
      • 💔变量分布在多个类中,维护表的任务量非常大.
    2. 使用运行时反射
      • ✔现成的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)
);
}

为类注册#

test_class.h
// 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::string
int 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 true
type::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 false
type::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 true
type::get<D>() == type::get<const D>(); // yields true
type::get<D>() == type::get(d2); // yields true
type::get<D>() == type::get<const D&>(); // yields true
type::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 true
type::get_by_name("bool") == type::get<int>(); // yields to false
type::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(); // true
type::get<D>().is_pointer(); // false
type::get<D*>().is_pointer(); // true
type::get<D>().is_array(); // false
type::get<D[50]>().is_array(); // true
type::get<std::vector<D>>().is_array(); // true
type::get<D>().is_arithmetic(); // false
type::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/
作者
LiH
发布于
2025-08-29
许可协议
CC BY-NC-SA 4.0