开篇

前置知识

  1. 优秀的C++语言能力
  • C++11
  • 模板
  • 对象内存模型
  • 基本的各种规则机制

优势

  1. 万物可追踪

  2. 通用的属性和接口

  3. 统一的内存分配释放

  4. 统一的序列化模型

  5. 统计功能

  6. 调试的便利

  7. 为反射提供便利

  8. UI编辑的便利

    代价

  9. 臃肿的Object

  10. 不必要的内存负担

  11. 多重继承的限制

  12. 类型系统的割裂

类型系统概述

引言

  • UE4的核心为反射系统
  • 反射系统 在 类型系统 上都构建
  • GC、序列号、编辑器支持在 反射系统 上搭建

类型系统(UClass)

  • 定义:程序运行空间内构建出来的类型信息树组织
  • 类型系统 是反射的底层,反射系统是在类型系统之上构建的
  • 没有反射系统,可以用静态导出类的方式也能实现动态生成类

C# 的类型系统

  • UE本身也是用C#作为编译UBT的实现语言

  • C# 获取类型信息

    Type type = obj.GetType();  //or typeof(MyClass)
  • C#反射

    image

    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++ RTTI (Run-Time Type Identification)

只提供两种基本操作

  1. typeid
  • 让用户知道是什么类型
  • 供一些基本对比和name方法
  • 一般来说也只是把它当作编译器提供的一个唯一类型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();
};
  1. dynamic_cast
  • 将一个指向派生类的基类指针或引用转换为派生类的指针或引用
  • 使用条件是只能用于含有虚函数的类
  • 转换引用失败会抛出bad_cast异常,转换指针失败会返回null
  • dynamic_cast内部机制其实也是利用虚函数表里的类型信息来判断一个基类指针是否指向一个派生类对象
  • 目的更多是用于在运行时判断对象指针是否为特定一个子类的对象
Base* base=new Derived();
Derived* p=dynamic_cast<Derived>(base);
if(p){...}else{...}

C++当前实现反射的方案

基本思想是采用手动标记。在程序中用手动的方式注册各个类,方法,数据

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

模板

#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);
}

编译器数据分析

  • 从编译器编译出来的pdb中文中获取类信息
  • 缺点:太过依赖平台

工具生成代码

  • 分析好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;
 };

UE里UHT的方案

  • 实现在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 NativeFuncTest();

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