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


已识乾坤大,犹怜草木青。长空送鸟印,留幻与人灵。 -马一浮《旷怡亭口占》