基于C++的反射框架研究与实现

2021-11-05 08:04杨树仁张启明孙为康王鸿显
电子技术与软件工程 2021年17期
关键词:枚举对象模板

杨树仁 张启明 孙为康 王鸿显

(中船航海科技有限责任公司 北京市 100071)

1 引言

目前,Java、C#等编程语言具有较为成熟的反射框架,在实践中受到了广泛应用。但是,C++语言本身并不支持这一动态特性。虽然,在C++新标准中增加了许多类型信息提取方式,增强了模板元编程能力,但是整体上还不是一套完整的反射框架。目前已有许多不同实现机理的C++反射框架,如QT、UE4 等,但是与高级语言相比仍存在较大的差距,具有一定的局限性。

因此,本文主要研究目的是基于C++语言建立一个结构简单、功能完备、性能高效的通用型反射框架,尽量涵盖高级编程语言的所有反射功能,为C++反射框架的标准化建设提供理论指导。

2 概述

元数据是描述数据的数据,类型元数据用于描述类型定义信息[2]。本文中将类型定义信息称为类型特征信息,将加工处理后的类型特征信息称为类型元数据。反射框架的主要任务是提取类型特征信息,存入元数据仓库,再通过对元数据的合理使用实现反射功能。

本文设计的反射框架主要包括特征信息提取、类型注册、元数据应用、对象系统四部分。特征信息提取和类型注册是反射框架的基础,元数据应用和对象系统是反射框架的成果,只有提取到更加丰富的类型特征,才能建立功能完备的反射框架。

3 特征信息提取

特征信息包括基础信息、函数信息、属性信息、注解信息、枚举信息、基类信息等,以模板元编程作为主要提取手段,在程序编译阶段提取特征信息,提高运行期效率。

3.1 基础信息

基础信息主要描述类型自身固有信息,如名称、内存大小、类别(枚举、指针、函数、容器)、特征(指针特征、装箱特征、元素特征)等。下面对几种信息进行重点介绍:

3.1.1 类型名称

本文基于内置宏(__FUNCSIG__或__PRETTY_FUNCTION__)提取类型名称,利用模板函数特化方式返回包含类型名称的字符串,然后在类型注册阶段加工提取,得到标准化类型名称。

3.1.2 指针特征

指针特征包括指针间址级数与一级指针信息。一级指针类型是间址级数为1 的指针类型,可以在无完整类型定义的情况下获取类型信息,适合作为多级指针的衔接点。

3.1.3 装箱特征

装箱是对象通用化的一种手段,是将对象存入通用对象(俗称:箱子)的过程。装箱特征,也称为装箱模式,用于描述对象在箱子内部的存储模式,分为值模式和共享模式。一般值模式对象存储于栈区,拷贝过程按值传递。共享模式对象存储于堆区,拷贝过程按引入传递。

3.1.4 容器类型

容器类型主要指STL 容器类型或满足容器判据的类型,如vector、list、map、QList 等。容器判据是判断类内是否定义const_iterator 与value_type 类型或别名。

3.1.5 元素特征

主要包括容器元素和模板元素两种特征。容器元素指容器内部存储类型,如map的元素类型为pair。模板元素类型,指模板参数类型,如pair的元素类型为T1 和T2。

元素特征信息提取主要采用分支模板的方式,针对不同类型获取相应的元素类型信息,也可以通过模板特化或偏特化的方式创建自定义的分支模板。

3.2 函数信息

类成员函数包括普通函数、静态函数和特殊函数(默认构造函数、拷贝构造函数、拷贝赋值函数、析构函数、垃圾回收函数、运算符重载函数、元数据取值函数、字符串序列化函数、哈希码取值函数、容器读写函数等)。

工厂模板的无参特化类型可自动区分不同函数类型,简化函数代理创建过程。工厂模板的有参偏特化类型可自动区分同名重载函数。最终,函数信息存储于函数代理中,以抽象类FunctionProxy作为显示调用接口。

特殊函数信息提取过程主要依靠编译器SFINAE 特性自动判定是否存在指定函数,然后利用代理函数提取函数信息。下面对几种特殊函数进行介绍:

3.2.1 构造函数

对象构造工厂模板是实现对象通用构造一种方法,通过引入代理函数解决了无法获取构造函数指针的问题,可以适用于任何类型对象的构造过程。在应用对象构造工厂前,先利用SFINAE 特性进行条件判定,仅条件成立才注册构造函数信息。对于平凡类型来说,由于构造规则比较简单,不必引入代理函数辅助。

3.2.2 垃圾回收函数

主要服务于内存管理的垃圾回收过程(GC)。当GC 过程判定对象不可达时,会自动执行对象的垃圾回收函数,提前通知对象,便于开发者主动释放相关资源。利用SFINAE 特性,当且仅当类存在垃圾回收函数时,才提取回收函数信息,模板判据是decltype(std::declval().Dispose())。

3.2.3 元数据取值函数

是类的虚函数成员,主要利用虚函数动态联编特性,运行阶段会动态选择合适的取值函数返回正确的类型元数据和实际的对象指针,有效地实现接口(或指针)转换。

3.3 属性信息

属性是面向对象编程的重要概念,用于表示对象的性质和关系。在编程语言中,属性通常具有三个要素:属性名、get 访问器、set访问器。成员变量是自带属性名和读写访问器的特殊属性。

3.3.1 成员变量信息

成员变量特征信息主要包括变量名、类型、偏移地址、静态特征。其中,变量名通过宏定义参数字符化方式获取,变量类型通过decltype 进行反向推导获取,静态特征通过模板is_member_object_pointer 进行判定,偏移地址通过表达式addressof(((T*)0)->name)获取。

3.3.2 属性信息提取

主要包括属性名、类型、set 访问器、get 访问器。通过宏定义的方式主动设定属性名与属性类型。set 访问器和get 访问器的获取方式与类成员函数信息获取方式相同。附加了静态检查功能,当访问器类型与属性类型不一致时,提示编译错误信息。

3.4 注解信息

注解是一种支持反射的信息标注,是一种更高级的代码注释,是对类型、属性、函数、枚举值等定义信息的补充说明。本文的注解与Java 注解概念一致,也等同于C#的特性。

从编程实现角度出发,注解信息是一种特殊对象,也包括对象类型和对象地址信息,将注解信息附加到指定类型的元数据内,就成为了这个类型特有信息,也称为类型特性。

3.5 枚举信息

枚举信息主要用于枚举值和枚举名的动态转换功能。QT 框架提供了Q_ENUMS 简化了枚举信息的提取过程,但是仅适用于具有Q_OBJECT 或Q_GADGET 声明的类内枚举类型,适用范围有限。

本文利用可变宏定义的方式,设计了一种更加简单有效的枚举信息提取方法,弥补了QT 无法提取类外枚举信息的不足。部分宏定义如下:

其中,DECL_ENUM 用于类内枚举类型的定义,DECL_ENUM_NS 用于类外枚举类型的定义。将InitHelper 与ValueHelper结合实现了自动提取枚举值列表功能,前者判定枚举值是否存在等号赋值,后者实现枚举值递增判定,利用函数ParseEnumTable 建立枚举值与枚举名的映射关系。

3.6 基类信息

基类信息用于描述类的继承关系,包括基类类型与地址偏移。由于基类类型无法通过类型推导、内置宏、模板元编程等方式自动提取,因此本文主要用宏定义方式实现基类注册,包括侵入式和非侵入式两种注册方式。地址偏移是子类与基类对象间的内存地址偏移,主要用于接口变换,可以通过表达式(int)(intptr_t)(static_cast((T*)1))-1 进行计算。

4 类型注册

类型注册是以元数据模型为基础,将特征信息加工处理生成元数据,再存入元数据仓库的过程。

4.1 元数据模型

元数据模型不仅是类型注册的基础,也是整个反射框架的基础。元数据以MetaData 作为基类,存储注解信息,并派生了类型元数据、属性元数据、函数元数据。类型元数据用于存储公用的类型信息,派生了指针类型元数据、类元数据、枚举类型元数据、函数类型元数据,分别存储各自的特征信息。注意区分函数类型元数据与函数元数据,前者主要存储函数类型的定义信息,后者主要存储代理函数信息。注意区分类型元数据与类元数据,前者为基类,后者仅存储class 或struct 类型的特征信息。

4.2 注册宏

类型注册的核心是MetaRegister,表现形式是注册宏。注册宏是简化类型注册的有效手段,辅助开发者快速完成类型反射部分代码的编写。

(1)类型注册宏:表达式为REG_TYPE(Type){…},其中类内的函数、属性、枚举、注解等信息注册均在大括号内完成。程序执行阶段的类型注册过程均会在main 函数前完成。

(2)函数注册宏:表达式为REG_FUNCTION(Name)或REG_FUNCTION(Name,Define),后者主要用于注册重载函数。

(3)构造注册宏:用于注册构造函数,表达式为REG_CTOR(ArgTypes),其中ArgTypes 表示形参类型。

(4)属性注册宏:表达式为 REG_PROPERTY(Name) 或REG_PROPERTY(Name,Type,[[Getter],[Setter]]),前者用于注册成员变量信息,后者的Getter 和Setter 表示可选的访问器函数名,可按需添加相应的访问器。

(5)注解注册宏:基本形式为ANNOTATION(Type(Args),[Fi eld=Value])。如果附加在类型注册宏后,表示为类型添加注解;附加在函数注册宏后,表示为函数添加注解;附加在属性注册宏后,表示为属性添加注解。

(6)枚举注册宏:表达式为DECL_ENUM(Type,Values)或DECL_ENUM_NS(Type,Values),前者用于定于类内枚举类型,后者用于定义类外枚举类型。

(7)基类注册宏:表达式为REG_BASE(Type,Bases)或REG_BASE_NS(Type,Bases),前者属于侵入式,后者属于非侵入式,表达的意思相同。

4.3 合法性检查

合法性检查是在程序运行期检查类型信息合法性,与编译阶段静态断言相结合,能够有效避免人为错误。

5 元数据应用

如图7所示,元数据模型对外提供了四个基础类型:MetaType、MetaProperty、MetaFunction 和MetaAnnotation,分别应用类型信息、属性信息、函数信息以及注解信息实现动态反射功能。其中MetaType 主要实现类型检索、动态构造、成员检索、基类检索、万能转换等功能;MetaProperty 主要实现属性动态读写功能;MetaFunction 主要实现函数动态调用功能;MetaAnnotation 主要实现注解信息动态读取功能。

6 对象系统

对象系统主要提供数据通用化封装方法,表现为对象装箱和拆箱操作。装箱是将对象封装为通用对象的过程。拆箱是将通用对象转换成指定类型对象或接口的过程。反射框架的重要应用成果,就是实现对通用对象的构建、传递、属性读写、函数调用等功能。

共享模式主要基于共享智能指针的思想实现的,通过引用计数器表示对象的使用情况,在计数器为0 时自动释放共享内存。然而,这种方式不可避免的会进入智能指针的依赖陷阱,即闭环引用问题。为了解决这个问题,本文引入了垃圾回收机制,主要基于标记清除算法,将不可达对象执行自动回收,可以有效回收闭环孤岛,同时采用分代回收策略提高垃圾回收效率。

7 结束语

本文基于C++语言设计了一个结构简单、性能高效、功能完备的通用版反射框架,具有特征信息提取、类型注册、元数据应用和对象系统四大主要模块,设计了有效的元数据模型以及对象系统模型,实现了万能转换、动态构造、动态属性读写、动态函数调用、注解反射、装箱拆箱、垃圾回收等高级功能,几乎涵盖了高级语言的所有反射特性。本文设计的反射框架要求编译器至少支持C++11标准,经测试,能够在GCC4.8.1 下正常编译运行。

猜你喜欢
枚举对象模板
神秘来电
基于理解性教学的信息技术教学案例研究
一种高效的概率图上Top-K极大团枚举算法
基于熵的快速扫描法的FNEA初始对象的生成方法
基于太阳影子定位枚举法模型的研究
区间对象族的可镇定性分析
铝模板在高层建筑施工中的应用
城市综改 可推广的模板较少
USB开发中易混淆的概念剖析