菜单

《InsideUE4》UObject(二)类型系统概述

2018年11月13日 - 注册免费送38元体验金

曾子曰:吾日三省吾身——为人谋而不忠乎?与情人到而非信乎?传不习乎?

引言

达同一篇我们叙到了以戏耍引擎,或者当次与高档编程语言中,设计一个统一对象模型得到的利,和假设交的代价,以及以UE里是怎对之尽量降低规避之。那么由本篇开始,我们就是从头谈论如何开始构建这样一个对象模型,并在这个之上逐渐扩大为适应发动机的各种功能需求的。

众目睽睽,一般娱乐引擎最底部面对的且是操作系统API,硬件SDK,所能借助到之家伙为反复只有发生C++本身。所以考虑于原生的C++基础及,搭建对象系统,往往得从头开始造轮子,最底部也是极致基本的编制当不可不得掌控在祥和之手中,以后升迁改良长效果为才会免给限制。

那么,从头开始的话语,Object系统来那基本上效益:GC,反射,序列化,编辑器支持……应该由哪一个上马?哪一个是少不了的?GC不是,因为很不了本人可一直new/delete或者智能指针引用技术,毕竟别的很多引擎也都是如此干的。序列化也无是,大未了每个子类里手写多少排布,麻烦是劳动,但是意义及啊是可以兑现的。编辑器支持,默认类对象,统计等都是事后额外附加的力量了。那你说反射为何是必需的?大多数游戏引擎用的C++没有反射,不呢都因此得漂亮的?确实为这么,不利用反射的那些效果,不动态根据项目创建对象,不遍历属性成员,不依据名字调用函数,大未了手写绕转,没有死的坎。但是既然上文已经论述了一个统一Object模型的利,那么只要以Object身上不添加反射,无疑就是比如是砍掉了Object的一律对翅膀,让其只能当地上走,而未能够以还宽空间内表达威力。还有其他一个点的考虑是,反射作为底层的体系,如果实现宏观了,也得以大大裨益其他系统的实现,比如来矣照,实现序列化起来就够呛便宜了;有没有产生反光,也关系及GC实现时的方案选,完全是片种套路。简单举个例,反射里对每个object有个class对象保存信息,所以理论及class身上就是足以保存有拖欠型的object指针引用,这个信息GC就好动用起来实现部分功能;而无是class对象的说话,GC的实现即得走别的方案路子了。所以说凡是先行实现反射,有了一个尤其朴实的靶子系统基础后,再在是之上实现GC才更的明智。

类型系统

则上述一直用反射的术语来讲述我们熟悉的那无异法运行时取得类型信息的系统,动态创建类对象等,但是实际“反射”只是当“类型系统”实现后的附加功能,人们往往都最过重视最后表露的劲力量,而将扎实的精神支撑让忘掉了。想想看,如果我实现了class类用来提供Object的类型信息,但是未提供动态创建,动态调用函数等功用,请问还来没有产生义?其实还还是是深有含义的,光光是提供了一个类型信息,就提供了一个Object之外的静态信息载体,也会构建起object之间的派生从属于涉,想想如果UE里去丢了因名字创办类对象的能力,是会见损失有利功能,但着实也尚从未到元气大伤的水准,GC依然会走得兴起。

故而后来更多为此“类型系统”这个更纯粹的术语来发挥object之外的类型信息构建,而因此“反射”这个术语来讲述运行时取类型的功用,通过类型信息反过来创建对象,读取修改属性,调用方法的功能行为。反射更多是同样栽行为能力,更偏于动词。类型系统指的凡程序运行空间内构建出的类型信息树组织,

C# Type

因为C++本身运行时路系统的累,所以我们第一将一个都落实宏观之言语,来探那最后收获是啊法。这里选择了C#一旦未是java,是盖自觉着C#比java更强大优雅(不争辩),Unity用C#当脚本语言,UE本身吗是用C#作编译UBT的落实语言。
在C#里,你可以由此以下一行代码方便之得到类型信息:

Type type = obj.GetType();  //or typeof(MyClass)

图片 1
本篇不是C#映教程(关心的和睦失去寻找相关学科),但此要略提一下我们用关怀的:

  1. Assembly是次集的意思,通常指的凡一个dll。
  2. Module是程序集里的子模块划分。
  3. Type就是我们太关心的Class对象了,完整描述了一个靶的类型信息。并且Type之间吧得由此BaseType,DeclaringType之类的性能来互形成Type关系图。
  4. ConstructorInfo描述了Type中的构造函数,可以透过调用它来调用特定的构造函数。
  5. EventInfo描述了Type中定义之event事件(UE中之delegate大概)
  6. FiedInfo描述了Type中之字段,就是C++的成员变量,得到之后好动态读取修改值
  7. PropertyInfo描述了Type中之属性,类比较C++中的get/set方法结合,得到后可获取设置属性值。
  8. MethodInfo描述了Type中的计。获得方式后哪怕可以动态调用了。
  9. ParameterInfo描述了智吃之一个个参数。
  10. Attributes指的是Type之上附加的表征,这个C++里并没有,可以省略明了吧接近及的定义的首任数据信息。

可观看C#里之Type几乎提供了全副信息数量,简直就是比如是拿编译器编译后底数量还让暴露出了被您。实际上C#的照还好供任何还高级的作用,比如运行时动态创建出新的类似,动态Emit编译代码,不过这些还是继言语了(在后头讲解蓝图时应有还见面涉嫌)。当前来说,我想读者们能有一个光景的印象就是是,用代码声明定义出来的项目,当然可以经一致种多少结构总体描述下,并以运作时再取得。

C++ RTTI

比方谈话到C++中的运作时路系统,我们一般会说RTTI(Run-Time Type
Identification),只提供了点滴只最好基本的操作符:

typeid

这个关键字之最主要意图就用来受用户知道是什么类型,并提供部分中坚对比与name方法,作用吧交多单是于用户判断从属于不同之种,所以实际说起来type_info的动并无广,一般的话呢只是将它们当编译器提供的一个唯一项目Id。

const std::type_info& info = typeid(MyClass);

class type_info
{
public:
    type_info(type_info const&) = delete;
    type_info& operator=(type_info const&) = delete;
    size_t hash_code() const throw();
    bool operator==(type_info const& _Other) const throw();
    bool operator!=(type_info const& _Other) const throw();
    bool before(type_info const& _Other) const throw();
    char const* name() const throw();
};

dynamic_cast

欠换符用于用一个对派生类的基类指针或引用转换为叫生类的指针或引用,使用标准是不得不用来含有虚函数的近乎。转换引用失败会抛出bad_cast异常,转换指针失败会回来null。

Base* base=new Derived();
Derived* p=dynamic_cast<Derived>(base);
if(p){...}else{...}

dynamic_cast内部机制其实呢是应用虚函数表里的类型信息来判定一个基类指针是否对一个差生类对象。其目的更多是用于在运转时判断目标指针是否为一定一个子类的靶子。

其余的仍用模板,宏标记就都是编译期的伎俩了。C++于RTTI方面也真是坏之软弱,传说被的业内反射提案也遥无期,所以大家便还得八仙过海各显神通,采用各种办法模拟实现了。C++都能用于去贯彻别的语言底层,不就是大半一个轱辘的事嘛。

C++时贯彻反射的方案

既然C++本身并未提供足够的类型信息,那我们不怕动用各种其他各种附加措施来采访,并构建保存起来之后供程序后使用。根据采集信息之主意各异,C++的反光方案吗发出以下流派:

核心思想是运用手动标记。在先后中因故手动的道注册各个类,方法,数据。大概就如这么:

struct Test
{
    Declare_Struct(Test);
    Define_Field(1, int, a)
    Define_Field(2, int, b)
    Define_Field(3, int, c)
    Define_Metadata(3)
};

于是宏偷梁换柱的把正常的声明换成自己之构造。简单可见这种方法还较的老,写起也生之繁琐。因此一再用底免多。更重要之是频繁用打破常规的修道,因此经常给扔掉。

模板

C++中的模版应该算是不过酷分别为别的语言的一个要命杀器,引导其精的编译器类型识别能力构建起相应的数据结构,理论及吗是好兑现有档次系统的一样部分功能。举一个Github实现比较优雅的C++RTTI反射库做例子:rttr

#include <rttr/registration>
using namespace rttr;
struct MyStruct { MyStruct() {}; void func(double) {}; int data; };
RTTR_REGISTRATION
{
    registration::class_<MyStruct>("MyStruct")
         .constructor<>()
         .property("data", &MyStruct::data)
         .method("func", &MyStruct::func);
}

说实话,这写得早就不行简洁优雅了。算得及是上了C++模板下之顶峰。但是足以视,仍然需要一个个之手动去定义类并拿走方式属性注册。优点是轻量程序外便会直接内嵌,缺点是匪吻合懒人。

编译器数据解析

还发头人便想到既然C++编译器编译完所有代码,那得是来完整类型信息数据的。那是否将她转换保存起来供程序下也?事实上这吗是行之,论@vczh的GacUI里就分析了VC编译生成后pdb文件,然后抽取出类型定义的音讯实现反射。VC确实为提供了IDiaDataSource
COM组件用来读取pdb文件之情节。用法可以参照:GacUI Demo:PDB
Viewer(分析pdb文件并取得C++类声明的详细内容)。
辩论及吧,只要您能赢得到同编译器同级别的类型信息,你多就如是清一色知晓了。但是缺点是分析编译器的生成数据,太过借助平台(比如不得不VC编译,换了Clang就是任何一样仿照方案),分析提取的长河反复也比累艰深,在正常的编译前需多加一个编译流程。但优点也是抱的数额最是完善。
这种方案为因为太过累,所以业内用的总人口未多。

工具转代码

理所当然之略人哪怕以想开,既然宏同模板的章程,太过辛苦。那我能免可知写一个家伙来机关就吗?只要分析好C++代码文件,或者分析编译器数据为行,然后据此预定义好的条条框框变化对应的C++代码来跟源文件对诺高达。
一个吓例子就是是Qt里面的反射:

#include <QObject>
class MyClass : public QObject
{
    Q_OBJECT
  Q_PROPERTY(int Member1 READ Member1 WRITE setMember1 )
  Q_PROPERTY(int Member2 READ Member2 WRITE setMember2 )
  Q_PROPERTY(QString MEMBER3 READ Member3 WRITE setMember3 )
  public:
      explicit MyClass(QObject *parent = 0);
  signals:
  public slots:
  public:
    Q_INVOKABLE int Member1();
    Q_INVOKABLE int Member2();
    Q_INVOKABLE QString Member3();
    Q_INVOKABLE void setMember1( int mem1 );
    Q_INVOKABLE void setMember2( int mem2 );
    Q_INVOKABLE void setMember3( const QString& mem3 );
    Q_INVOKABLE int func( QString flag );
  private:
    int m_member1;
    int m_member2;
    QString m_member3;
 };

盖过程是Qt利用基于moc(meta object
compiler)实现,用一个头版对象编译器在次编译前,分析C++源文件,识别部分独特的宏Q_OBJECT、Q_PROPERTY、Q_INVOKABLE……然后变对应的moc文件,之后再度一并全部编译链接。

UE里UHT的方案

决不多说,你们啊能体悟UE当前的方案为是如此,实现在C++源文件中空的宏做标记,然后用UHT分析生成generated.h/.cpp文件,之后还一同编译。

UCLASS()
class HELLO_API UMyClass : public UObject
{
    GENERATED_BODY()
public:
    UPROPERTY(BlueprintReadWrite, Category = "Test")
    float Score;

    UFUNCTION(BlueprintCallable, Category = "Test")
    void CallableFuncTest();

    UFUNCTION(BlueprintNativeEvent, Category = "Test")
    void NavtiveFuncTest();

    UFUNCTION(BlueprintImplementableEvent, Category = "Test")
    void ImplementableFuncTest();
};

这种办法的长处是力所能及比较粗的针对性C++代码做修改,所要召开的只是于代码里加一些空标记,并没损坏原来的好像声明结构,而且会为比和谐的艺术把第一数据与代码关联在一块,生成的代码再复杂呢得针对用户隐藏起来。一方面分析源码得力的讲话能够获取同编译器差不多的音,还会透过自己的组成部分从定义标记来供更多酷成代码的指点。缺点是实现起来实在呢是充分累的,完整的C++的语法分析往往是顶尖复杂的,所以限制是好写的分析器只能分析有简约的C++语法规则与宏标记,如果用户以比较复杂的语法时候,比如用#if
/#endif包裹有宣称,就会让自己的分析器出错了,还好这种状态不多。关于多同浅编译的题材,也得透过自定义编译器的编译脚本UBT来躲避。

苟是熟悉C#的意中人,一眼就能够看下就同C#的Attribute的语法简直差不多一型一样,所以UE也是收到了C#语法反射的有些淡雅写法,并利用上了C++的宏魔法,当然生成的代码里模板肯定啊是必不可少的。采取众长最后确定了这种类型信息搜集方案。

总结

本篇主要是解释了怎么设因项目系统作为搭建Object系统的第一步,并勾画了C#语言里到之种系统看起是呀样子,接着讨论了C++当前的RTTI工具,然后环顾一下时C++业内的各种反射方案。知道别人小好之是什么样子,知道好现在手里来什么,知道当前业内别人家是怎尝试解决此问题之,才会心中有数知道为何UE选择了脚下之方案,知道UE的当即套方案以正儿八经算是什么水平。

还说把废话,笔者从认为想解释清楚一宗东西,更多之应当是说清楚背后的各种概念。否则对在源码,罗列出来各个类,说一下每个接口的图,数据交互怎么引用,流程是怎跑的,你能生快之就是懂得相同非常堆信息。你只是是懂了What,How,但是还挡不停止别人问一样句子Why。而功力的升官就在于问一个个why中,A办法能做,B办法啊尽,那为什么最后择了C方法?想要回应是问题,你尽管得朔古至今,旁征博引,了解各种法子的观,优劣点,偏重倾向,综合起来才还好的展开衡量。而计划,就是衡量的主意。这么写起也确确实实发接触款,但是个人权衡一下或系统性更加的根本。宁愿慢点,质量第一。

下卷,我们将启幕讲述在UE里是怎么组织类型信息数据。

引用

  1. std::type_info
  2. How Qt Signals and Slots
    Work

UE4.14.1


知乎专栏:InsideUE4
UE4深入上QQ群:456247757(非新手入门群,请预上了官方文档和视频教程)
微信公众号:aboutue,关于UE的满新闻资讯、技巧问答、文章发表,欢迎关注。
私家原创,未经授权,谢绝转载!

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图