zoukankan      html  css  js  c++  java
  • ue4 NewObject/StaticConstructObject_Internal/StaticAllocateObject/FObjectInitializer:对象创建和初始化

    UObject是一套很复杂的体系,之前读ue3代码时曾分析过其类型系统实现,主要是与UClass间的关系

    现在转到ue4,发现那一块其实差不多,于是再重点备忘一下UObject本身的创建和初始化过程

    1、首先,用NewObject<>来创建一个新对象:

    template< class T >
    T* NewObject(UObject* Outer, UClass* Class, FName Name = NAME_None, EObjectFlags Flags = RF_NoFlags, UObject* Template = nullptr, bool bCopyTransientsFromClassDefaults = false, FObjectInstancingGraph* InInstanceGraph = nullptr)
    {
        if (Name == NAME_None)
        {
            FObjectInitializer::AssertIfInConstructor(Outer, TEXT("NewObject with empty name can't be used to create default subobjects (inside of UObject derived class constructor) as it produces inconsistent object names. Use ObjectInitializer.CreateDefaultSuobject<> instead."));
        }
    
    #if DO_CHECK
        // Class was specified explicitly, so needs to be validated
        CheckIsClassChildOf_Internal(T::StaticClass(), Class);
    #endif
    
        return static_cast<T*>(StaticConstructObject_Internal(Class, Outer, Name, Flags, EInternalObjectFlags::None, Template, bCopyTransientsFromClassDefaults, InInstanceGraph));
    }

    除了一些条件检测,直接调了以前老版的对应物StaticConstructObject_Internal。

    这里的几个参数,有意思的是第5个参数Template,通过它可以复制对象。

    2、StaticConstructObject_Internal,这里面主要做两件事:

    一是用StaticAllocateObject来分配空间,

    二是在此空间上初始化对象。

    3、StaticAllocateObject:

    首先是检查要创建新对象,还是替换一个原有对象,因为一路传进来的有个Name参数。如果找到一个已有的同名对象,那么会强制那个对象析构,然后重用它的空间,否则的话,就会直接分配空间了:

    UObjectBase* FUObjectAllocator::AllocateUObject(int32 Size, int32 Alignment, bool bAllowPermanent)

    除了这件主要的工作,还有不少其它的事,比如管理对象之间的连接、在异步线程中创建对象时的通知等等,暂时不深究了。

    4、有了前者分配的空间,那么就可以在此处构造对象了:

         Result = StaticAllocateObject(InClass, InOuter, InName, InFlags, InternalSetFlags, bCanRecycleSubobjects, &bRecycledSubobject);
    (*InClass->ClassConstructor)( FObjectInitializer(Result, InTemplate, bCopyTransientsFromClassDefaults, true, InInstanceGraph) );

    这里ClassConstructor是每个UClass都有的一个函数指针类成员变量,实际上所有的UClass里该指针都指向一个全局模板函数:

    template<class T>
    void InternalConstructor( const FObjectInitializer& X )
    { 
        T::__DefaultConstructor(X);
    }

    而每个类里的__DefaultConstructor也是用宏统一生成的,内容也是简单的转发给new:

    static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())TClass(X); }

    这个new的形式很不平凡,既有传给operator new的【(EInternal*)X.GetObj()】,又有传给该类实际构造函数的【FObjectInitializer& X】

    后者正是InClass->ClassConstructor需要的实参,而前者也是通过宏(DECLARE_CLASS)定义在每个UObject里的一个operator new:

    inline void* operator new( const size_t InSize, EInternal* InMem ) 
        { 
            return (void*)InMem; 
        }

    总结一下上面绕来绕去的东西就是:

    StaticAllocateObject分配了内存空间Result,然后以此为参数(当然还有其它的参数)构造了一个FObjectInitializer X,其中X.GetObj返回的就是这个内存地址Result;

    接着用【 new(Result) TClass(X) 】来构造对象,指明地址在Result上,并将X做为参数传给其构造函数。

    5、关于FObjectInitializer

    接上步,在构造函数返回后,其临时参数【FObjectInitializer& X】是要自动析构的,因而ue4利用此步骤,在这里面做了很多事,大部份的事都是与该体系内部状态管理有关的,暂时难以透彻理解。

    但有一个的操作与应用相关,即属性初始化,这是通过InitProperties来完成的:

    void FObjectInitializer::InitProperties(UObject* Obj, UClass* DefaultsClass, UObject* DefaultData, bool bCopyTransientsFromClassDefaults)
    {
      ……
        UClass* Class = Obj->GetClass();
      ……if (!bNeedInitialize && bCanUsePostConstructLink)
        {
            // This is just a fast path for the below in the common case that we are not doing a duplicate or initializing a CDO and this is all native.
            // We only do it if the DefaultData object is NOT a CDO of the object that's being initialized. CDO data is already initialized in the
            // object's constructor.
            if (DefaultData)
            {
                if (Class->GetDefaultObject(false) != DefaultData)
                {
                    QUICK_SCOPE_CYCLE_COUNTER(STAT_InitProperties_FromTemplate);
                    for (UProperty* P = Class->PropertyLink; P; P = P->PropertyLinkNext)
                    {
                        P->CopyCompleteValue_InContainer(Obj, DefaultData);
                    }
                }
                else
                {
                    QUICK_SCOPE_CYCLE_COUNTER(STAT_InitProperties_ConfigEtcOnly);
                    // Copy all properties that require additional initialization (e.g. CPF_Config).
                    for (UProperty* P = Class->PostConstructLink; P; P = P->PostConstructLinkNext)
                    {
                        P->CopyCompleteValue_InContainer(Obj, DefaultData);
                    }
                }
            }
      ……

    在这里通过遍历UClass上记录的属性元数据,可以对当前实例的每个属性进行赋值。

    有趣的是DefaultData这个参数,也就是最早的Template参数,一路辗转到此。当然如果Template为空的话,这里的DefaultData就是该类的CDO了。

    代码里明显体现出对此两种情况的不同处理策略:

    如果是指定了要复制的对象Template->DefaultData,那么要遍历类上的所有的属性,因为对于一个实际的复制目标,你不知道它哪些属性已经改变了(不是默认值 ),因此必须全盘复制

    而如果只是从CDO复制的话,那么只需要处理该类里明确指定过可能有初始化状态的字段,如打上CPF_Config标记的字段,它们在启动时会去ini文件里提取相应的配置值。

  • 相关阅读:
    面试题目
    MyEclipse 启动 tomcat时错误处理
    js 剪贴板操作
    PHP面试题
    MySQL 数据库 source 导入乱码
    php 不用通过表单也能创建HTTPpost请求
    一个css中zindex的用法
    最新黑链代码expression:隐藏链接代码
    测试网站访问速度的几个小方法
    优秀开源外贸网店程序一览
  • 原文地址:https://www.cnblogs.com/wellbye/p/5808894.html
Copyright © 2011-2022 走看看