菜单

注册免费送38元体验金《InsideUE4》UObject(二)类型系统概述

2018年11月17日 - 注册免费送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)

注册免费送38元体验金 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#的爱人,一眼便可知看出来马上注册免费送38元体验金和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地图