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
并不是一个指向类构造函数的指针,它指向的是InternalConstructor
。InternalConstructor
中调用的内容涉及到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 = 0
,bool = 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问题也不存在了
由于FObjectInitializer
是NewObject
过程中创建出来的临时对象,那么它的析构中也会参与初始化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分支
SpawnActor
是UWorld
中的方法。在生成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
。当世界被加载的时候,它的Outer
是UPackage
;而世界中的关卡的Outer
是UWorld
;关卡中的AActor
的Outer
是ULevel
;Actor上挂载的UActorComponent
的Outer
是AActor
对于蓝图类而言,它所在的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
GetOuter
与GetOwner
是两个概念的东西,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_AutonomousProxy
的APawn
来说,它的Owner
是APlayerController
;对于ROLE_SimulatedProxy
的APawn
来说,它的Owner是nullptr
。对于AActorComponent
来说,它们的Outer和Owner都是拥有它们的AActor
Difference between Outer and Owner
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);