[UE4] TSharedPtr, TWeakObjectPtr and TUniquePtr
TSharedPtr
, TWeakPtr
and TUniquePtr
imitated from shared_ptr
, weak_ptr
and unique_ptr
of C++11.
TSharedPtr
Summary
TSharedPtr
is the counter of reference of raw objects (Non-UObject). The count that TSharedPtr
reference to would increase one while TSharedPtr
was assigned once, the object would be destroyed when the count of reference is 0.
Usage:
TSharedPtr<FRawClass> ObjPtr = MakeShareable(new FRawClass());
Example
RawClass.h
#pragma once
#include "CoreMinimal.h"
class TP_54_API FRawClass
{
public:
FRawClass()
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Cyan, TEXT("=== Construct"));
}
~FRawClass()
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, TEXT("=== Destruct"));
}
};
MyTestGameMode.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "MyTestGameMode.generated.h"
class FRawClass;
UCLASS(minimalapi)
class AMyTestGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
AMyTestGameMode();
UFUNCTION(Exec)
void CreatePtr();
UFUNCTION(Exec)
void ReleaseFoo();
UFUNCTION(Exec)
void ReleaseBar();
protected:
TSharedPtr<FRawClass> Foo = nullptr;
TSharedPtr<FRawClass> Bar = nullptr;
};
MyTestGameMode.cpp
#include "RawClass.h"
#include "Templates/SharedPointer.h"
void AMyTestGameMode::CreatePtr()
{
Foo = MakeShareable(new FRawClass());
Bar = Foo;
}
void AMyTestGameMode::ReleaseFoo()
{
Foo = nullptr;
}
void AMyTestGameMode::ReleaseBar()
{
Bar = nullptr;
}
Console command for testing:
CreatePtr //screen prints: === Construct
ReleaseFoo //nothing happens
ReleaseBar //screen prints: === Destruct
Cautions
Objects would never be destroyed if two TSharedPtr
, which are reference to a same object, were assigned to each other, resulting a memory leak.
If the class of TSharedPtr contains lambda functions, you must be careful to ensure the current object is valid while executing lambda, because the object ref counter may reach to zero which causes the destruction in the gap time between current function and lambda function.
For more details, see: Shared Pointer in Async Task
Whether if TSharedPtr works for UObject or not
As the comments of TSharedPtr
say, it only works for raw pointers
:
/** Proxy structure for implicitly converting raw pointers to shared/weak pointers */
// NOTE: The following is an Unreal extension to standard shared_ptr behavior
template< class ObjectType >
struct FRawPtrProxy
{
/** The object pointer */
ObjectType* Object;
/** Reference controller used to destroy the object */
FReferenceControllerBase* ReferenceController;
/** Construct implicitly from an object */
FORCEINLINE FRawPtrProxy( ObjectType* InObject )
: Object ( InObject )
, ReferenceController( NewDefaultReferenceController( InObject ) )
{
}
/** Construct implicitly from an object and a custom deleter */
template< class Deleter >
FORCEINLINE FRawPtrProxy( ObjectType* InObject, Deleter&& InDeleter )
: Object ( InObject )
, ReferenceController( NewCustomReferenceController( InObject, Forward< Deleter >( InDeleter ) ) )
{
}
};
In theory, Unreal GC system is to UObject
what std::shared_ptr<>
is to raw pointer, so TSharedPtr
doesn’t need to work for UObject
.
But, there’s no limitation for counting UObject
in the source code of SharedPointerInternals.h
in engine.
If an UObject
was passed as constructor’s argument of TSharedPtr
:
void ATestPlayerController::TestFun()
{
TestActor = NewObject<AActor>(this);
TSharedPtr<AActor> SharePtr2 = MakeShareable(TestActor);
}
application will crash after jumping out of function TestFun
:
Assertion failed: GetFName() == NAME_None [File:D:/Build/++UE4/Sync/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectBase.cpp] [Line: 136]
UE4Editor_Core!AssertFailedImplV() [d:\build\++ue4\sync\engine\source\runtime\core\private\misc\assertionmacros.cpp:105]
UE4Editor_Core!FDebug::CheckVerifyFailedImpl() [d:\build\++ue4\sync\engine\source\runtime\core\private\misc\assertionmacros.cpp:455]
UE4Editor_CoreUObject!UObjectBase::~UObjectBase() [d:\build\++ue4\sync\engine\source\runtime\coreuobject\private\uobject\uobjectbase.cpp:136]
UE4Editor_Engine!AActor::`vector deleting destructor'()
UE4Editor_TestTD4_Win64_DebugGame!SharedPointerInternals::TReferenceControllerWithDeleter<AActor,SharedPointerInternals::DefaultDeleter<AActor> >::DestroyObject() [D:\Program_Files\Epic Games\UE_4.24\Engine\Source\Runtime\Core\Public\Templates\SharedPointerInternals.h:116]
UE4Editor_TestTD4_Win64_DebugGame!TSharedPtr<AActor,0>::~TSharedPtr<AActor,0>()
UE4Editor_TestTD4_Win64_DebugGame!ATestPlayerController::TestFun() [D:\workspace\unreal_dev\TestTD4\Source\TestTD4\TestTD4PlayerController.cpp:124]
As shown above: If TSharedPtr
reference to UObject
, it works at compiling, initialization of TSharedPtr
also works at run-time, but it crashed on destruction.
How to add raw pointers into GC of Unreal
If want to add raw pointers into GC system of UE4, raw class must inherits from FGCObject
.
Example:
class FAppleARKitVideoOverlay : public FGCObject
{
public:
FAppleARKitVideoOverlay();
virtual ~FAppleARKitVideoOverlay();
TWeakPtr
Example
RawClass.h
#pragma once
#include "CoreMinimal.h"
class MYTEST_API FRawClass
{
public:
FRawClass()
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Cyan, TEXT("=== Construct"));
}
~FRawClass()
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, TEXT("=== Destruct"));
}
};
MyTestGameMode.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "MyTestGameMode.generated.h"
class FRawClass;
UCLASS(minimalapi)
class AMyTestGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
AMyTestGameMode();
UFUNCTION(Exec)
void CreatePtr();
UFUNCTION(Exec)
void ReleaseFoo();
UFUNCTION(Exec)
void ReleaseBar();
UFUNCTION(Exec)
void KeepBaz();
UFUNCTION(Exec)
void ReleaseQux();
UFUNCTION(Exec)
void CheckBaz();
protected:
TSharedPtr<FRawClass> Foo = nullptr;
TSharedPtr<FRawClass> Bar = nullptr;
TWeakPtr<FRawClass> Baz = nullptr;
TSharedPtr<FRawClass> Qux = nullptr;
};
MyTestGameMode.cpp
#include "RawClass.h"
#include "Templates/SharedPointer.h"
void AMyTestGameMode::CreatePtr()
{
Foo = MakeShareable(new FRawClass());
Bar = Foo;
Baz = Foo;
//Baz = new FRawClass(); //compilation error: weak ptr must be initialized with share ptr.
}
void AMyTestGameMode::ReleaseFoo()
{
Foo = nullptr;
}
void AMyTestGameMode::ReleaseBar()
{
Bar = nullptr;
}
void AMyTestGameMode::KeepBaz()
{
//create a new TSharedPtr from TWeakPtr to keep the object ref.
Qux = Baz.Pin();
}
void AMyTestGameMode::ReleaseQux()
{
Qux = nullptr;
}
void AMyTestGameMode::CheckBaz()
{
FString Info = Baz.IsValid() ? TEXT("Valid+++") : TEXT("Invalid---");
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Cyan, Info);
}
Console command for testing weak ptr:
CreatePtr //screen prints: === Construct
ReleaseFoo //nothing happens
CheckBaz //screen prints: Valid+++
ReleaseBar //screen prints: === Destruct
CheckBaz //screen prints: Invalid---
Console command for testing Pin function:
CreatePtr //screen prints: === Construct
ReleaseFoo //nothing happens
CheckBaz //screen prints: Valid+++
KeepBaz //creat a new shared ptr to keep object ref
ReleaseBar //nothing happens
ReleaseQux //screen prints: === Destruct
CheckBaz //screen prints: Invalid---
TUniquePtr (Unique Pointers)
Summary
A Unique Pointer solely and explicitly owns the object it references. Since there can only be one Unique Pointer to a given resource, Unique Pointers can transfer ownership, but cannot share it. Any attempts to copy a Unique Pointer will result in a compile error. When a Unique Pointer is goes out of scope, it will automatically delete the object it references.
TUniquePtr
works for Non-UObject, there’s compilation error while initializing TUniquePtr
with UObject
.
Example : Basic
Define:
TUniquePtr<FMeshTriOctree> MTOctree;
Instantiate:
MTOctree = MakeUnique<FMeshTriOctree>(Bounds.GetCenter(), Bounds.GetExtent().GetMax());
Pass reference between two TUniquePtr:
TUniquePtr<FMeshTriOctree> MTOctree02 = MoveTemp(MTOctree);
//TUniquePtr<FMeshTriOctree> MTOctree02 = MTOctree; // compilation error
If you assign to new TUniquePtr without MoveTemp
, you will get error:
error C2280: 'TUniquePtr<FMeshTriOctree,TDefaultDelete<T>>::TUniquePtr(const TUniquePtr<T,TDefaultDelete<T>> &)': attempting to reference a deleted function
Example : TUniquePtr & TQueue
Add element:
class FTestStruct
{
};
TQueue<TUniquePtr<FTestStruct>> TestQueue;
TUniquePtr<FTestStruct> Ptr = MakeUnique<FTestStruct>();
TestQueue.Enqueue(MoveTemp(Ptr));
If forget to invoke MoveTemp
, e.g.:
TQueue<TUniquePtr<FTestStruct>> TestQueue;
TUniquePtr<FTestStruct> Ptr = MakeUnique<FTestStruct>();
TestQueue.Enqueue(Ptr);
You will get error:
D:\UE_4.22\Engine\Source\Runtime\Core\Public\Containers/Queue.h(252): error C2248: 'TUniquePtr<FTestStruct,TDefaultDelete<T>>::TUniquePtr': cannot access private member declared in class 'TUniquePtr<FTestStruct,TDefaultDelete<T>>'
Remove element:
1st way:
TUniquePtr<FTestStruct> Ptr = MoveTemp(TestQueue.Peek());
TestQueue.pop();
2nd way (recommended) :
TUniquePtr<FTestStruct> Buffer;
TestQueue.Dequeue(Buffer)
Example : TUniquePtr & TArray
Define:
TArray<TWeakObjectPtr<APawn>> TestArray;
Add Element:
TUniquePtr<FTestStruct> Ptr = MakeUnique<FTestStruct>();
TestArray.Add(MoveTemp(Ptr));
Remove element:
1st way:
TUniquePtr<FTestStruct> Ptr = MoveTemp(TestArray[TestArray.Num() - 1]);
TestArray.RemoveAt(TestArray.Num() - 1);
2nd way (recommended) :
TUniquePtr<FTestStruct> Ptr = TestArray.Pop();
TWeakObjectPtr
Summary
The memory (UObjects only, not raw objects) presented by TWeakObjectPtr
can’t be prevented from garbage collecting. Once the object that was presented by TWeakObjectPtr
was destroyed in other place, the inner pointer of TWeakObjectPtr
would be assigned as nullptr
automatically, and TWeakObjectPtr::IsValid()
would return false
. But TSharedPtr
has no features of TWeakObjectPtr
.
TWeakObjectPtr vs TWeakPtr
Difference:
- TWeakObjectPtr works for UObjects only, TWeakPtr works for raw objects (Non-UObject) only;
- TWeakObjectPtr can works independently with UObjects, while TWeakPtr must work with TSharedPtr.
Usage
Assignment
TWeakObjectPtr<AActor> MyWeakActor;
MyWeakActor = MyActor;
Get value
AActor* Actor = MyWeakActor.Get();
or
if(MyWeakActor.Get())
{
ACharacter* Character = Cast<ACharacter>(MyWeakActor);
}
if MyActor
has been destroyed, MyWeakActor.Get()
returns nullptr
.
MyActor->Destroy();
bool IsNull = MyWeakActor.Get() == nullptr; //true
bool IsValid = MyWeakActor.IsValid(); //false
if MyActor
has been recycled by GC, Cast<AMyCharacter>(MyActor)
would cause crash after a while, but Cast<AMyCharacter>(MyWeakActor)
would not.
Remove in Array
Examples:
TArray<TWeakObjectPtr<APawn>> TestArray;
APawn* TestPawn01 = GetWorld()->SpawnActor<APawn>(APawn::StaticClass(), FVector(100.f, 100.f, 0.f), FRotator::ZeroRotator);
APawn* TestPawn02 = GetWorld()->SpawnActor<APawn>(APawn::StaticClass(), FVector(200.f, 200.f, 0.f), FRotator::ZeroRotator);
TestArray.Add(TWeakObjectPtr<APawn>(TestPawn01));
TestArray.Add(TWeakObjectPtr<APawn>(TestPawn02));
int Num = TestArray.Num(); // 2
TWeakObjectPtr<APawn> WeakPtr1(TestPawn01);
TestArray.Remove(WeakPtr1);
int Num2 = TestArray.Num(); // 1
TWeakObjectPtr<APawn> WeakPtr2(TestPawn01);
TestArray.Remove(WeakPtr2);
int Num3 = TestArray.Num(); // 1
TestArray.Remove(TestPawn02);
int Num4 = TestArray.Num(); // 0
Reference
unreal-unique-pointer
https://baemincheon.github.io/2020/03/14/unreal-unique-pointer/
UE4 TSharedPtr和UObject的垃圾回收
http://www.v5xy.com/?p=808
There’s a Huge Difference, One Will Always Crash
https://answers.unrealengine.com/questions/48818/whats-the-difference-between-using-tweakobjectptr.html
what is a “weak object pointer”?
https://answers.unrealengine.com/questions/201186/what-is-a-weak-object-pointer.html
Unreal Smart Pointer Library
https://docs.unrealengine.com/en-US/Programming/UnrealArchitecture/SmartPointerLibrary/index.html
TSharedPtr、UniquePtr、TWeakPtr、TSharedRef
https://www.cnblogs.com/shiroe/p/14729821.html
已识乾坤大,犹怜草木青。长空送鸟印,留幻与人灵。 -马一浮《旷怡亭口占》