UObject系统的GC
UPROPERTY 引用
当我们在一个UObject类声明各种继承UObject的 变量时,得加UPROPERTY(), 这个可以让UE4帮我们自动管理UObject的垃圾回收(引用计数)。UPROPERTY不仅仅用于反射变量到编辑器上编辑,也涉及UObject变量的GC。
如下面所示:
UCLASS(config=Game)
class AMyProject1Character : public ACharacter
{
GENERATED_BODY()
private:
UPROPERTY()
UObject* object;
}
TWeakObjectPtr
UE4里自创一套C++ GC规则,官方并不推荐使用C++标准库。 学C++的同学可能会对弱指针熟悉,可以访问一个对象又不造成引用计数加1。通常定义在一个类内,用于获取其他对象的某个变量当成临时变量方便访问。如下面所示:
UCLASS()
class MYPROJECT3_API ATestActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ATestActor();
UPROPERTY(VisibleAnywhere)
class UCameraComponent* cameraComponent;
};
ATestActor::ATestActor()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
cameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
cameraComponent->SetupAttachment(RootComponent);
}
UCLASS(config=Game)
class AMyProject3Character : public ACharacter
{
GENERATED_BODY()
public:
TWeakObjectPtr<UCameraComponent> cameraComponent;
protected:
virtual void BeginPlay() override;
}
void AMyProject3Character::BeginPlay()
{
Super::BeginPlay();
TArray<AActor*> arrayActor;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ATestActor::StaticClass(), arrayActor);
if (arrayActor.Num() > 0)
{
ATestActor* testActor = Cast<ATestActor>(arrayActor[0]);
if (nullptr == testActor)
return;
cameraComponent = testActor->cameraComponent;
}
}
这里CameraComponent组件创建与Te'stActor,但是AMyproject2Character需要保存临时变量的时候就用弱指针保存。
局部创建的UObject对象GC
局部创建并且在局部使用的UObject要调用AddToRoot来防止被GC, 然后 RemoveFromRoot来移除引用能得到GC
UCLASS()
class MYPROJECT4_API UTestObject : public UObject
{
GENERATED_BODY()
public:
UTestObject()
{
}
float a;
};
void AMyProject4Character::BeginPlay()
{
Super::BeginPlay();
UTestObject* testObject = NewObject<UTestObject>(this);
testObject->AddToRoot();
testObject->a = 1.0f;
UE_LOG(LogTemp, Error, TEXT("xxxx = %f"), testObject->a);
testObject->RemoveFromRoot();
}
普通结构体Struct的GC
不继承UObject和UStruct对象的结构体的GC有下面几条
TSharedPtr
共享智能指针,类似C++标准库的共享智能指针,不能用于UObject对象(TWeakPtr倒是可以用于UObject), 因为UObject有自己 专门GC的一套规则。总之TSharedPtr用于自定义的结构体(不继承UObject)。
如下所示:
class MYPROJECT2_API FTest
{
public:
FTest();
~FTest();
};
#include "FTest.h"
FTest::FTest()
{
UE_LOG(LogTemp, Warning, TEXT("1111FTest Constrction"));
}
FTest::~FTest()
{
UE_LOG(LogTemp, Warning, TEXT("2222FTest Deconstrction"));
}
UCLASS()
class MYPROJECT2_API ATestActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ATestActor();
TSharedPtr<FTest> Test;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
}
// Called when the game starts or when spawned
void ATestActor::BeginPlay()
{
Super::BeginPlay();
Test = MakeShareable(new FTest());
}
PIE运行时Actor创建:
PIE取消运行Actor销毁:
如果是局部变量的结构体创建TSharedPtr:
void ATestActor::BeginPlay()
{
Super::BeginPlay();
TSharedPtr<FTest> Test;
Test = MakeShareable(new FTest());
}
FGCObject
上面我们说到了UObject的情况,采用NewObject创建和UPROPERTY()标记来进行GC。而不继承UObject的自定义结构体采用MakeShareable来创建SharedPtr来管理GC。这时候一个特殊的情况出现了玩意,一个不继承UObject的结构体中出现UObject对象怎么办?如下面所示:
*/
UCLASS(BlueprintType, notplaceable, MinimalAPI)
class UCameraAnim : public UObject
{
。。。。。。。。。。。。。。。。。。。。。。。。
}
class UCameraAnim;
/**
*
*/
class MYPROJECT2_API FTest
{
public:
FTest();
~FTest();
UPROPERTY()
UCameraAnim* CameraAnim;
};
这种情况下,我试过,如果用MakeShareable创建结构体指针,然后直接操作结构体的UObject对象进行NewObjec创建,结构体本身的访问没问题,但是结构体里面的UObject对象一段时间后自动GC掉,成为野指针,不能访问。这种情况怎么解决呢?UE4为我们提供了FGCObject ,我们的结构体继承FGCObject ,然后用AddReferencedObjects 方法引用结构体里的所有UObject对象,这里的UObject对象不需要被UPROPERTY()标记, 如下所示
#include "GCObject.h"
class UCameraAnim;
/**
*
*/
class MYPROJECT2_API FTest :public FGCObject
{
public:
FTest();
~FTest();
UCameraAnim* CameraAnim;
protected:
virtual void AddReferencedObjects(FReferenceCollector& Collector) override
{
Collector.AddReferencedObject(CameraAnim);
}
};
TSharedRef
本质上共享引用(TSharedRef)和共享指针(TSharedPtr)是差不多的,一样是用于非UObject体系的对象,具备引用计数器,不过TSharedRef和TSharedPtr的很大不同在于,共享引用无法引用空对象,就像C++ 声明空引用,编译直接报错。TSharedRef连
“IsValid”判断是否非空都没有,强逼你使用必定是有效的引用。所以在UE4 的SWidget编程大量使用TSharedRef<SWidget>
如 int& a;
正确的声明定义
TSharedRef<FMyCustom> MyCustom = MakeShared<FMyCustom>();
错误的声明定义:
TSharedRef<FMyCustom> MyCustom;
TSharedRef<FMyCustom> MyCustom = nullptr;
TSharedRef没有空引用,应该持有的对象必须是有效的
TSharedPtr转为TSharedRef
TSharedPtr<FMyCustom> aaa = MakeShareable(new FMyCustom);
TSharedRef<FMyCustom> a = aaa.ToSharedRef();
一个普通的结构体使用TSharedFromThis继承,就可以拥有AsShared函数让普通的结构体对象转为TSharedRef, 像SWidget这个类本身就使用了TSharedFromThis
struct FMyCustom : TSharedFromThis<FMyCustom>
{
int a = 1;
FMyCustom()
{
UE_LOG(LogTemp, Error, TEXT("destruct a = %d"), a);
}
~FMyCustom()
{
UE_LOG(LogTemp, Error, TEXT("undestruct a = %d"), a);
}
};
FMyCustom MyCustom;
RefCustom = MyCustom.AsShared();
TSharedRef可以转化为TSharedPtr, 直接等于就可以了
TSharedRef<FMyCustom> RefCustom = MakeShared<FMyCustom>();
TSharedPtr<FMyCustom> PtrCustom = RefCustom;
TWeakPtr
上面我们说到了 TWeakObjectPtr 为专门用于UObject, 相应的普通结构体也有弱指针TWeakPtr, 能够访问一个非UObject对象,但是又不造成引用计数+1
一般来说通过TWeakPtr来访问其持有的对象,先是用pin转换为TSharePtr指针,判断对象是否有效后,再访问,而不是直接通过弱指针的IsValid,然后直接访问
struct FMyCustom
{
int a = 1;
FMyCustom()
{
UE_LOG(LogTemp, Error, TEXT("destruct a = %d"), a);
}
~FMyCustom()
{
UE_LOG(LogTemp, Error, TEXT("undestruct a = %d"), a);
}
};
UCLASS()
class MYPROJECT5_API AMyActor : public AActor
{
public:
TWeakPtr<FMyCustom> WeakCustom;
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
}
void AMyActor::BeginPlay()
{
Super::BeginPlay();
TSharedPtr<FMyCustom> ShareCustom = MakeShareable(new FMyCustom);
WeakCustom = ShareCustom;
}
// Called every frame
void AMyActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
TSharedPtr<FMyCustom> ShareCustom = WeakCustom.Pin();
if (ShareCustom.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("a = %d"), ShareCustom->a);
}
}
很显然,弱指针没有引用到局部的智能创建的对象,所以没有“Tick a = 1”的log
TSharedPtr,TSharedRef, TWeakPtr在容器使用事项
得注意:弱指针(TWeakPtr)对象不能作为Set或者Map的Key,因为一个对象被GC掉无法通知一个容器的Key, 像TMap<TWeakPtr<FMyCustom>, int>就是不正当的使用手法,当然TWeakPtr可以作为容器的Value, TMap<int,TWeakPtr<FMyCustom>>毫无问题。
而TSharedPtr和TSharedRef既可以当作容器的Key,也可以作为容器的Value.
参考资料
[1] https://docs.unrealengine.com/en-US/Programming/UnrealArchitecture/SmartPointerLibrary/index.html