跳转至

从《光·遇》浅谈其中元编程和 C++ 反射

201 2021-02-18 22:30:30


关于元编程

元编程是一种编程技术,在这种技术中,计算机程序能够将其他程序作为它们的数据。这意味着一个程序可以被设计成在运行时读取、生成、分析或转换其他程序,甚至修改自身。在某些情况下,这能让程序员用最小量的代码行数解决问题,从而减少开发时间,并且它还能让程序具有更大的灵活性来有效地处理新的情况,而不需要重新编译。

元编程可能够将计算从运行时移动到编译时,使用编译时计算生成代码,并支持自修改代码。元程序所使用的语言称为元语言。被操纵的程序的语言称为面向属性的编程语言。一种编程语言成为自己的元语言的能力被称为反射。

️️️应用

回到具体项目,《光·遇》一共有两个框架涉及到元编程:

sky-metaprogramming

第一套是MetaSystem

它实现了 C++ 的反射,C++ 反射的应用主要有以下几个:

  • 通过 C++ 反射实现了 lua->c++ 的桥梁(lua binding)
  • 项目中的 Module 系统通过 C++ 反射实现各个子module的函数调用
  • 将 CP 自研 Maya 插件导出来的资源进行解析和加载也是用到 C++ 的反射

其他比如解析从服务器传输的数据也用到了 C++ 反射进行对应类的实例化。

第二套是tgc_ini

它主要实现了 C++ -> JAVA 的 JNI 桥梁框架。

其中,不管是 C++ 反射还是 JNI 框架,都用到了 C++ 模板元编程技术(TMP),另外 JNI 还使用 C++ 11的新特性 constexpr。实际上,C++ 模板最初是为实现泛型编程设计的,但偶然的发现让人们认识到C++ 模板居然是图灵完备的,所以模板元编程其实是“意外”功能,而不是设计的功能,这也是 C++ 模板元编程语法丑陋的根源。模板元编程的发现使得 C++ 成为两层次语言,执行编译计算的静态代码,执行运行期计算的动态代码,C++ 的静态代码由模板以及宏实现。

实现 C++ 反射

sky-reflection-in-cplusplus

如上图所示,这是 C++ 反射的基本功能,包括:

  • 可以获得已注册类的类信息,如果该类是自定义类(非基础类型 int,bool...),信息则包括:成员变量、成员函数和继承关系。
  • 可以获得全局变量信息
  • 可以获得全局函数信息

Module 模块例子

我们从用法推导需求再推导原理,然后用简单例子的实现来展示,项目中一个比较典型的用法例子为上述2中的 Module 模块,Module 类是项目中所有模块的基类,比如有网络相关的 NetModule,UI 相关的 MenuBar,还有 HttpClient、PickUpAbleBar 等等模块,它们都是继承自 Module 类,并由 ModuleBar 类总管所有子 Module 的生命周期和内存分配。而 ModuleBar 在调用子 Module 们的生命周期函数比如 Update 的时候是这么调用的:

m_moudleBarn->CallFunction( "Update", m_moduleFilter, ModuleBarn::Direction_Forward );

后面两个参数可以不用关注,主要关注第一个参数是个字符串 "Update"。

然后我们再看看几个子 Module 类的 Update 函数例子:

void MenuBarn::Update( Game* game)
void NetModule::Update( Game* game, AccountBarn* accountBarn, HttpClient, SystemIO* systemIO )
void HttpClient::Update( DnsResolver* dnsResolver )
void PickUpAbleBarn::Update( Camera* camera, Game* game )

可以看到,这几个子 Module 的 Update 函数参数个数和参数类型都不一样,那么问题来了,如何通过一个字符串 "Update" 就能调用各个子 Module 的 Update 函数并且能自动搜集每个不同 Update 需要的参数个数和参数类型对应的实例,并且填充进去呢?

所以接下来要解决的是(假设已经有了所有子 Module 的实例):

  • 根据字符串找到函数地址;
  • 得到每个函数的参数个数和参数类型信息;
  • 根据参数类型信息自动获取所需参数类型的实例;
  • 自动填充参数并调用。

Bilibili 查看原文