灵活的使用C ++中的反射系统( 二 )


如上所示,在函数内部还有另外三个对的调用reflect::TypeResolver<>::get() 。每个人都会找到的反映成员的类型描述符Node 。这些调用使用C ++ 11的decltype说明符自动将正确的类型传递给TypeResolver模板 。
查找类型描述符(请注意,本节中的所有内容都在reflect名称空间中定义 。)
TypeResolver是一个类模板 。当你调用TypeResolver<T>::get()特定类型T,编译器实例化相应的回报功能TypeDescriptor的T 。它适用于反射结构以及这些结构的每个反射成员 。默认情况下,这是通过主模板进行的,如下所示 。
默认情况下,如果T是包含REFLECT()宏之类的结构(或类),如Node中get()将返回指向该结构Reflection成员的指针-这就是我们想要的 。对于其他所有类型T,get()则调用getPrimitiveDescriptor<T>处理原始类型的函数模板,例如intstd::string
//声明处理原始类型(例如int,std :: string等)的函数模板:template <typename T>TypeDescriptor* getPrimitiveDescriptor();//以不同方式查找TypeDescriptor的帮助器类:struct DefaultResolver {...//如果T具有名为“ Reflection”的静态成员变量,则调用此版本:template <typename T, /* 在SFINAE 处 */>static TypeDescriptor* get() {return &T::Reflection;}//否则称为此版本:template <typename T, /* 在SFINAE 处 */>static TypeDescriptor* get() {return getPrimitiveDescriptor<T>();}};//这是查找所有TypeDescriptor的主要类模板:template <typename T>struct TypeResolver {static TypeDescriptor* get() {return DefaultResolver::get<T>();}};T使用SFINAE可以实现这一点的编译时逻辑:根据是否存在静态成员变量生成不同的代码 。我从上面的代码段中省略了SFINAE代码,坦率来讲,看上去虽然很丑陋,但是可以在源代码中检查实际的实现 。使用可以更优雅地重写其中的一部分if constexpr,但是我的目标是C ++ 11 。即使这样,T至少可以在C ++采用静态反射之前,检测是否具有特定成员变量的部分 。
TypeDescriptor的结构【灵活的使用C ++中的反射系统】在示例项目中,每个项目TypeDescriptor都有一个名称,大小和几个虚拟函数:
struct TypeDescriptor {const char* name;size_t size;TypeDescriptor(const char* name, size_t size) : name{name}, size{size} {}virtual ~TypeDescriptor() {}virtual std::string getFullName() const { return name; }virtual void dump(const void* obj, int indentLevel = 0) const = 0;};示例项目永远不会TypeDescriptor直接创建对象 。而是由系统创建从派生的类型的对象TypeDescriptor 。这样一来,每个类型描述符可以根据容纳额外的信息 。
例如,返回的对象的实际类型TypeResolver<Node>::get()为TypeDescriptor_Struct 。它有一个附加的成员变量,members其中包含有关的每个反映成员的信息Node 。对于每个反映成员,都有一个指向另一个成员的指针TypeDescriptor,整个过程在内存中就是这样子 。我在TypeDescriptor用红色圈出了各个子类:

灵活的使用C ++中的反射系统

文章插图
 
在运行时,可以通过调用getFullName()其类型描述符来获取任何类型的全名 。大多数子类仅使用getFullName()返回的基类实现TypeDescriptor::name 。在此示例中,唯一的例外是采用TypeDescriptor_StdVector描述std::vector<>专业化的子类 。为了返回完整的类型名称,例如"std::vector<Node>",它保留了指向其项目类型的类型描述符的指针 。我们可以在上面的内存图中看到这一点:有一个TypeDescriptor_StdVector对象,其itemType成员一直指向的类型描述符Node 。
当然,类型描述符仅描述类型 。为了对运行时对象进行完整的描述,我们既需要类型描述符,也需要指向对象本身的指针 。
请注意,TypeDescriptor::dump()该对象接受指向的指针const void* 。这是因为抽象TypeDescriptor接口要能处理任何对象类型,子类实现只需要确定其所需的类型即可 。例如,这个实现TypeDescriptor_StdString::dump() 。将转换const void*为const std::string* 。
virtual void dump(const void* obj, int /*unused*/) const override {std::cout << "std::string{"" << *(const std::string*) obj << ""}";}至于,以void这种方式强制转换指针是否安全 。显然,如果传入了无效的指针,则程序很可能崩溃 。这就是为什么在我的游戏引擎中,void指针所代表的对象总是与它们的类型描述符成对出现 。通过以这种方式表示对象,可以编写多种通用算法 。
在示例项目中,是将对象转储到控制台输出实现功能的,其实我们还能实现将对象类型描述符运用作二进制格式的序列化框架中 。


推荐阅读