Loading

UE-创建对象

new

对于非继承自UObject类而言,直接使用new在堆上分配对象,同时需要手动管理对象的生命周期,可以考虑使用智能指针

NewObject

Class Default Object

当引擎处于初始化阶段(PreInit)时,会为每个继承自UObject的对象创建CDO对象。CDO对象中记载了类成员的默认值(在类构造函数中初始化的值),供UE的反射系统使用。举个例子,UE知道类的属性哪些是被蓝图修改过的,哪些是C++中设定的默认值,就是通过CDO去实现的。CDO的是一个UObject*

在引擎的PreInit阶段中,会去加载项目中的Module,然后对项目中Module包含的UObject都创建一个CDO对象

/**
 * Get the default object from the class
 * @param	bCreateIfNeeded if true (default) then the CDO is created if it is null
 * @return		the CDO for this class
*/
UObject* GetDefaultObject(bool bCreateIfNeeded = true) const
{
    if (ClassDefaultObject == nullptr && bCreateIfNeeded)
    {
        const_cast<UClass*>(this)->CreateDefaultObject();
    }
    return ClassDefaultObject;
}

__DefaultConstruct

NewObject的调用中,该代码完成了对指定类构造函数的调用

(*InClass->ClassConstructor)(FObjectInitializer(Result, Params));
typedef void (*ClassConstructorType) (const FObjectInitializer&);
ClassConstructorType ClassConstructor;

ClassConstructor并不是一个指向类构造函数的指针,它指向的是InternalConstructorInternalConstructor中调用的内容涉及到UHT对.generated.h的生成

// 模板参数T对应NewObject<T>
template<class T>
void InternalConstructor( const FObjectInitializer& X )
{ 
	T::__DefaultConstructor(X);
}

__DefaultConstructor属于类静态函数,由宏来定义。而该宏定义于GENERATED_BODY()GENERATED_BODY_LEGACY()中。这个宏所干的事情就是对UObject进行一次placement new。同时由于是placement new,并且用于初始化的内存上在申请的时候已被置零,所以类中的基本类型成员都将具备默认值,如int = 0bool = false,并不会出现因构造函数没有初始化而导致的成员不确定初始值的问题

// ObjectMacros.h
#define DEFINE_DEFAULT_CONSTRUCTOR_CALL(TClass) \
	static void __DefaultConstructor(const FObjectInitializer& X) { \
		new((EInternal*)X.GetObj())TClass; \
	}

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

其实在UObject中,已经对这个静态函数进行了定义,但此方法通常会被子类覆盖

class COREUOBJECT_API UObject : public UObjectBaseUtility
{
   DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(UObject)
};

当类中只有默认构造函数和类中具有FObjectInitializer构造函数时,GENERATED_BODY()所包含的内容是不同的

当类中只含有默认构造函数时,宏经过简化可以展开如下(以AActor为例)

#define GENERATED_BODY() \
public: \
	DEFINE_DEFAULT_CONSTRUCTOR_CALL(AActor)
#define GENERATED_BODY_LEGACY() \
	NO_API AActor(const FObjectInitializer& ObjectInitializer); \
	DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AActor) \

当类中具有FObjectInitializer构造函数时,宏经过简化可以展开如下(以AActor为例)

#define GENERATED_BODY() \
public: \
	DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AActor)
#define GENERATED_BODY_LEGACY() \
	NO_API AActor(const FObjectInitializer& ObjectInitializer); \
	DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AActor) \

那么得出结论,FObjectInitializer构造函数的优先级是高于默认构造函数的

StaticConstructObject_Internal

template< class T >
T* NewObject(UObject* Outer = (UObject*)GetTransientPackage())
{
    // ...
	FStaticConstructObjectParameters Params(T::StaticClass());
	Params.Outer = Outer;
	return static_cast<T*>(StaticConstructObject_Internal(Params));
}

可以看到NewObject主要就是对StaticConstructObject_Internal进行一次调用,StaticConstructObject_Internal的调用栈如下

NewObject的过程中进行了两次placement new,两次调用中调用到了UObjectBase构造函数的不同重载版本。第一次placement new完成了将UObject添加到全局对象池的操作,第二次属于对象构造正常对父类构造函数的调用。如此在两次placement new中不存在析构调用的ub问题也不存在了

由于FObjectInitializerNewObject过程中创建出来的临时对象,那么它的析构中也会参与初始化UObject数据的工作。这一步会根据反射信息初始化属性字段,子对象,从Config中加载属性等等

(*InClass->ClassConstructor)(FObjectInitializer(Result, Params));
/**
 * Destructor for internal class to finalize UObject creation (initialize properties) after the real C++ constructor is called.
 **/
FObjectInitializer::~FObjectInitializer() { /* ... */ }

SpawnActor

Actor的生命周期可以参照官方文档中给出的一张图,这里主要分析其中的SpawnActor分支

SpawnActorUWorld中的方法。在生成Actor之前首先会进行一些列有效性检测,如传进来的参数是否为nullptr,生成的对象是否为抽象类,生成的位置是否有效等等

该函数主要执行三个功能

  • 调用NewObject创建对象

  • 将Actor添加到Level中,可用于后续迭代器遍历时访问

  • 进行Actor类初始化的流程,同时调用委托进行通知。由上流程图可以得出,AActor的Construction,以及组件的初始化等流程都是在Actor->PostSpawnInitialize中完成的

  • 调用OnActorSpawned,代表SpawnActor流程调用完毕

现在来看一下Actor->PostSpawnInitialize中究竟执行了什么,官方中给出了一个大致的流程如下

// General flow here is like so
// - Actor sets up the basics.
// - Actor gets PreInitializeComponents()
// - Actor constructs itself, after which its components should be fully assembled
// - Actor components get OnComponentCreated
// - Actor components get InitializeComponent
// - Actor gets PostInitializeComponents() once everything is set up
//
// This should be the same sequence for deferred or nondeferred spawning.

// It's not safe to call UWorld accessor functions till the world info has been spawned.
  • 首先Actor会设置好自身的Owner,Instigator等

  • 对自身挂载的组件进行注册,即调用RegisterAllComponents(注意Register和Initialize是两回事)

  • 调用虚函数PostActorCreated

  • 调用FinishSpawning。这其中会调用ExecuteConstruction(这一步会调用蓝图中的Construction Script以及C++中的OnConstruction),PostActorConstruction(这一步会对组件进行Initialize,然后调用它自身的和组件的BeginPlay)。需要注意的是前中后三步InitializeComponents只有AActor拥有,UActorComponent中只有InitializeComponent这一个

总的来讲,当一个Actor被Spawn出来时,它的流程为:PreSpawn->Components Register->Construction->Components Initialization->BeginPlay

FObjectInitializer

Outer

Outer可以理解为是在对象生命周期上的Owner。任何UObject最上层Outer都是UPackage。当世界被加载的时候,它的OuterUPackage;而世界中的关卡的OuterUWorld;关卡中的AActorOuterULevel;Actor上挂载的UActorComponentOuterAActor

对于蓝图类而言,它所在的Package是对应蓝图的.uasset文件;对于场景中蓝图实例而言,他所在的Package是对应的地图文件.map

// UObejctBase.h
/** Returns the UObject this object resides in */
FORCEINLINE UObject* GetOuter() const
{
    return OuterPrivate;
}

How can I understand the data member ‘Outer’ in the UObjectBase class

GetOuterGetOwner是两个概念的东西,Owner是对于AActor而言的,它被定义在AActor.h

// AActor.h
/** Get the owner of this Actor, used primarily for network replication. */
UFUNCTION(BlueprintCallable, Category=Actor)
AActor* GetOwner() const;

对于ROLE_AutonomousProxyAPawn来说,它的OwnerAPlayerController;对于ROLE_SimulatedProxyAPawn来说,它的Owner是nullptr。对于AActorComponent来说,它们的Outer和Owner都是拥有它们的AActor

What is owner?

Difference between Outer and Owner

Outer? Owner? AttachToActor/Component?

UE4 C++基础 - 资源常见名词解释

代码中的各种get

Template

Template也称作ObjectArchetype,当调用NewObject时,可以指定创建对象的原型

T* NewObject(UObject* Outer, 
             FName Name, 
             EObjectFlags Flags = RF_NoFlags, 
             UObject* Template = nullptr, 
             bool bCopyTransientsFromClassDefaults = false, 
             FObjectInstancingGraph* InInstanceGraph = nullptr);

而当参数传递到FObjectInitializer层时,它被称作ObjectArchetype

FObjectInitializer(UObject* InObj,
                   UObject* InObjectArchetype,
                   EObjectInitializerOptions InOptions,
                   struct FObjectInstancingGraph* InInstanceGraph = nullptr);

当被传入FObjectInitializer析构函数中的InitProperties时,它被称作DefaultData。若此时的Template为空,那么传递进函数的其实是CDO

void FObjectInitializer::InitProperties(UObject* Obj,
                                        UClass* DefaultsClass,
                                        UObject* DefaultData,
                                        bool bCopyTransientsFromClassDefaults);

Property Copy

posted @ 2022-08-03 14:10  _FeiFei  阅读(1313)  评论(0编辑  收藏  举报