C++反射是什么?
我个人理解的是运行时获取类的各种信息,类的成员变量,类的成员函数,更具体的说是运行时一个类知道自己有几个属性,知道每个属性的名字是什么, 可以通过名字运行时访问以及调用类的各种数据,这就是C++反射。当然非类的变量函数也是属于C++反射的范围,不过这里主要指代是Class(struct)相关的反射。看下面一段RTTR示例代码
#include <iostream>
#include<rttr/type>
#include <rttr/registration>
using namespace rttr;
class MyStruct
{
public:
MyStruct()
{
};
void func(double a)
{
printf("a = %f\n", a);
};
int data;
RTTR_ENABLE()
};
class TestA : public MyStruct
{
public:
TestA()
{
}
RTTR_ENABLE(MyStruct)
};
class TestB : public TestA
{
public:
TestB()
{
}
RTTR_ENABLE(TestA)
};
//手动注册属性方法和构造函数
RTTR_REGISTRATION
{
registration::class_<MyStruct>("MyStruct")
.constructor<>()
.property("adata", &MyStruct::data)
(
metadata("TOOL_TIP", "1121`Set the name of node."),
metadata("TOOL_TIP1", "1121`Set the name of node.")
)
.method("func", &MyStruct::func);
registration::class_<TestA>("TestA")
.constructor<>();
registration::class_<TestB>("TestB")
.constructor<>();
}
int main() {
//遍历类的成员
type t = type::get<MyStruct>();
for (auto& prop : t.get_properties())
std::cout << "name: " << prop.get_name() << std::endl;
for (auto& meth : t.get_methods())
std::cout << "name: " << meth.get_name() << std::endl;
//创建类型的实例
type t2 = type::get_by_name("MyStruct");
variant var = t2.create(); // 方式1
constructor ctor = t2.get_constructor(); // 方式2
var = ctor.invoke();
std::cout << "112= " << var.get_type().get_name() << std::endl; // 打印类型名称
//设置/获取属性
MyStruct obj;
property prop = type::get(obj).get_property("adata");
prop.set_value(obj, 23);
variant var_prop = prop.get_value(obj);
std::cout << "metadata= " << prop.get_metadata("TOOL_TIP").to_string() << std::endl; // prints '23'
std::cout << "1srfwe44= "<< prop.get_type().get_name() << std::endl; // prints '23'
//调用方法
MyStruct obj2;
method meth = type::get(obj2).get_method("func");
meth.invoke(obj2, 42.0);
type a = type::get(obj2);
variant var2 = a.create();
meth.invoke(var2, 42.0);
type b = type::get<TestB>();
printf("b name is %s\n", b.get_name());
array_range<type> baseClasses = b.get_base_classes();
if (t.is_base_of(a))
{
printf("1111111111111111\n");
}
for (auto &baseclass : baseClasses)
{
printf("base class name is %s\n", baseclass.get_name());
}
auto ss = &MyStruct::data;
std::cout << "fsdd11111= " << typeid(a).name() << std::endl; // prints '23'
MyStruct aassdd;
type cc = type::get(aassdd);
std::cout << "aassdd= " << cc.get_name() << std::endl; // prints '23'
system("pause");
return 0;
}
输出:
RTTR的核心数据结构
type_register_private---------type
type-----property,method,metadata
下面的各种分析都是以上面代码例子来分析
C++反射-类型(type)的实现
RTTR数据类型类为 type,记录了“int,float,class等类型的类型数据”,比如 class MyStruct 的类型数据, 记录了类 MyStruct 的名字为“MyStruct ”
类型数据(type)的类型结构
类型数据(type)的注册流程
上面是大致的简略版调用函数栈, 简约归为:在运行时,获取一个类的type_data, 然后构造出type, 最后把type存储到type_private_data单例管理里。可以看到m_src_name_to_id和m_custom_name_to_id是两个map, 以类型名为key,以type为value.
m_src_name_to_id存放的key是类型的原名,m_custom_name_to_id存放的key是后定制的名
这里有个非常重要的执行过程,构造type_data的过程,因为type_data存着type最重要的数据, 也就是类型的原生名
RTTR反射框架获取类型原名是统统编辑器内置宏来获取的,比如MSVC的__FUNCSIG__,GUNC的__PRETTY_FUNCTION__,CLANG或者APPLECLANG的__PRETTY_FUNCTION__,下面主要是实现代码
类型数据(type)的相关接口的实现
type::get(T&& object)
主要是模板推导, 从object推导其类型,就可以从type_private_data从遍历寻找相应的type数据
type::get_by_name(string_view name)
直接从type_private_data中遍历m_custom_name_to_id哈希表来查找type
C++反射-属性(property)的实现
类内的各种变量为属性, 类属性指针的表现比较特别。看看下面的例子,可以看出每个类内的变量类型是以类为前缀的,比如 MyStruct 的 int 变量成员类型就是int MyStruct::*, 我们可以通过类的实例对象和类变量指针进行访问。
类属性数据(property)的数据结构
类属性数据(property)的注册流程
上面是大致的简略版调用函数栈,简略归纳为 声明获取property的虚函数的接口基类property_wrapper_base。通过property_wapper模板类继承接口类,并利用模板推导一个属性为 “m_acc: A (C::*)”。而接口基类指针存在property里,property访问真正的类属性指针就忽视了类型,直接通过property_wrapper_base接口就能访问。这样既可以让class_data保存一个整齐无模板的property数组,又让property可以间接包含了类属性指针。
类型数据(property)的相关接口的实现
property get_property(string_view name)
实现上就是从type_data的class_data中property数组中遍历寻找
C++反射-方法(method)的实现
类内的各种执行函数为方法, 类方法指针和上面所说的类属性指针类似。看看下面的例子,可以看出每个类内的函数指针是以类为前缀的,比如 MyStruct 的 PrintA 函数类型就是 void (MyStruct::*)(int), 我们可以通过类的实例对象和类变量指针进行访问。
类函数数据(method)的数据结构
RTTR框架中 method的数据结构和property的数据结构非常类似
类方法数据(method)的注册流程
上面是大致的简略版调用函数栈, 过程和property的非常类似。简略归纳为 声明获取method各种基础属性的虚函数的接口基类method_wrapper_base。通过method_wapper模板类继承接口类,并利用模板推导一个函数类型为 “F”。而接口基类指针存在method里,method访问真正的函数指针变量就忽视了类型,直接通过property_wrapper_base接口就能访问。这样既可以让class_data保存一个整齐无模板的method数组,又让method可以间接包含了类函数指针。
类型数据(method)的相关接口的实现
method get_method(string_view name)
实现和get_property的类似,都是从class_data中m_methods数组中遍历查找。
类型(type)的子类型(derived_class), 父类型(base_class)注册
个人举得 这部分是RTTR框架实现得最精彩的部分,先看案例代码。下面 RTTR_ENABLE 将 TestA父类注册为MyStrcut和A, 换句话说,同时也将MyStrcut 和 A的子类注册为TestA
derived_class和base_class的数据结构
derived_class和base_class的注册流程
base_class 和 derived_class的发生在创建一个type的type_data的时候
这里 DerivedClass为目前创建类型type数据的类, 也就是示例代码的“TestA”. "has_base_class_list"假设一个类声明了base_class_list类型,就进行类型列表的递归编译(其中某些类型可能也声明了RTTR_ENABLE,存在类型列表),在静态函数中对每一个继承类型进行递归查找他们相应的type,然后加入vector<type>中,最终为type的type_data数据生成了base_classes(std::vector<type>)
这里最大的亮点在于:编译期进行递归,生成查找注册的类型列表中所有类型的代码。
当然此时type_data的class_data还没具备m_base_types和m_derived_types,子类型和父类型的实现是在注册type到 type_private_data单例管理类的时候,具体参考上面 “type生成的函数栈”。
value
metadata的实现
无论是类型的元数据,属性的元数据,方法的元数据,本质是就是key-value的map。实现方式很多,在RTTR里 property和mehod的元数据实现都是在相应的wrapper类继承metahandle, 没啥特殊的,所以就不详细分析实现原理。
参考资料
(1)__FUNCSIG__、__FUNCDNAME__、__FUNCTION__、__func__、__PRETTY_FUNCTION__