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文件里提取相应的配置值。

 

posted on 2016-08-26 01:04  冷欺花  阅读(5560)  评论(0编辑  收藏  举报

导航