Is this the correct practice with UObjects and smart pointers?

Hello,

it’s hard to find some documentation for this use case, and it’s not yet very clear when to choose to use the various shared pointers, this is why I ask here if you think I did the things right:

I have a class UBBTeam inheriting from UObject, which has a reference to an other team, and holds a static array of weak pointers to some players (ABBCharacter).
Each ABBCharacter is spawned regularly in the game mode. Moreover, each ABBCharacter holds a reference to another ABBCharacter, and a reference to his owning team.
The owner of the teams is my ABBGameMode, which holds them in a static array of shared pointer.

Here are the interesting excerpts of the classes, where I have put in comments my various questions :

ABBMatchGameMode class declaration

UCLASS(minimalapi)
class ABBMatchGameMode : public AGameMode
{
	GENERATED_UCLASS_BODY()

public:

    virtual void PostInitializeComponents() OVERRIDE;

private:

    // Is this the best way to store UObjects? Should I use UProperty() ?
    TStaticArray<TSharedPtr<UBBTeam>, 2> Teamtable;
};

ABBMatchGameMode class implementation

void ABBMatchGameMode::PostInitializeComponents()
{
    Super::PostInitializeComponents();

    for ( int index = 0; index < 2; index++ )
    {
        // Not sure about that, but it seems to be the only way. But maybe TSharedPtr is not well suited for that?
        TSharedPtr<UBBTeam>
            team( NewObject<UBBTeam>( ) );

        Teamtable[ index ] = team;
    }

    Teamtable[ 0 ]->SetOtherTeam( *Teamtable[ 1 ] );
    Teamtable[ 1 ]->SetOtherTeam( *Teamtable[ 0 ] );
}

UBBTeam class declaration:

UCLASS()
class UBBTeam : public UObject
{
    GENERATED_UCLASS_BODY()

public:

    void SetPlayer( ABBCharacter & player, const int team_position_index );
    void SetOtherTeam( const UBBTeam & other_team );

private:

    // This one should be OK, I saw plenty of examples like this in the engine
    TStaticArray<TWeakObjectPtr<ABBCharacter>, 2> PlayersTable;

    // Is it okay to use TWeakObjectPtr for UObject?
    TWeakObjectPtr<UBBTeam> OtherTeam;
};

UBBTeam class implementation:

void UBBTeam::SetPlayer( ABBCharacter & player, const int team_position_index )
{
    PlayersTable[ team_position_index ] = &player;
    player.SetTeam( *this );
}

// I prefer to pass C++ references generally. Should I use TSharedRef instead?
void UBBTeam::SetOtherTeam( const UBBTeam & other_team )
{
    check( &other_team != this );

    OtherTeam = &other_team;
}

ABBCharacter class declaration:

UCLASS(config=Game)
class ABBCharacter : public ACharacter
{
    GENERATED_UCLASS_BODY()

public:

    void SetTeam( const class UBBTeam & team );
    void SetTeamMate( const ABBCharacter & team_mate );

private:

    // Again, TWeakObjectPtr for both Actors and UObject. No problem?
    TWeakObjectPtr<class ABBCharacter> TeamMate;
    TWeakObjectPtr<class UBBTeam> Team;
};

ABBCharacter class implementation:

// C++ reference for a UObject, is it ok?
void ABBCharacter::SetTeam( const UBBTeam & team )
{
    check( !Team.IsValid() );

    Team = &team;
}

// C++ reference for a Actor, is it ok?
void ABBCharacter::SetTeamMate( const ABBCharacter & team_mate )
{
    check( !TeamMate.IsValid( ) );

    TeamMate = &team_mate;
}

What do you think?

In the shooter game, I could see:

UPROPERTY(Transient, Replicated)
TArray<class AShooterWeapon*> Inventory;

UPROPERTY(Transient)
TArray<UMaterialInstanceDynamic*> MeshMIDs;

Does this mean that it is converted automatically to smart pointers under the hood?

Thanks :slight_smile:

You don’t want to use TSharedPtr/TSharedRef/TWeakPtr with UObjects, those are designed for non UObjects.

UObjects use a garbage collection system. By declaring UPROPERTY() that essentially lets the garbage collection system know about that reference. If you declare just a UObject *, that is a Hard reference, which means that the referenced object will not be freed as long as their is a chain of references from the Root to that object. Here’s an overview of the basics: Unreal Object Handling in Unreal Engine | Unreal Engine 5.1 Documentation

So, if you want to reference a UObject, but don’t want it to be a Hard reference that keeps it from being garbage collected, you then use TWeakObjectPtr. This will automatically be set to NULL when the referenced object is deleted, but it won’t stop that object from getting deleted if no other references to it exist. This is useful for things like UI where you want to know about the state of an object, but can gracefully handle it going away when it dies.

This is not correct - UObject * doesn’t make a Hard/Root reference.
UE4 has no idea about UObject * so it doesn’t affect garbage collection, meaning that if object was garbage collected UObject * is not going to get nulled, which would cause all sorts of errors and issues. TWeakObjectPtr helps with that is it will be invalidated when the object is GCed.

To prevent object from garbage collection if it’s not referenced anywhere, you can call AdddToRoot() on it.

1 Like

You are wrong. UObject pointers automatically have their references cleaned up on destruction when they are marked as uproperties. It says so right in the link he gave you under the “Automatic Updating of References” section. What you say is only true if you don’t mark them as UProperties.

I think the phrasing of this answer is misleading for people who aren’t already familiar with these types of systems (reference counting, GC, preprocessor macros, etc).

Example:
“By declaring UPROPERTY() that essentially lets the garbage collection system know about that reference.” <= Correct

“If you declare just a UObject *, that is a Hard reference, which means that the referenced object will not be freed as long as their is a chain of references from the Root to that object.” <= Correct, only given the previous statement, but the phrasing of the sentence implied to me that UPROPERTY was not needed for a hard reference (even though I knew that it must be).

Something like:
If you declare a UObject * it is a raw C++ reference that the UE garbage collector will not know about (aka an unsafe pointer). The UObject may be collected, the pointer will not be NULL, and there will be no way to determine if the pointer is safe to use (hence unsafe pointer).

Marking the UObject * (unsafe pointer) with the UPROPERTY macro will automatically inform the GC of the reference - implicitly making it a hard reference (strong pointer). A UObject can not be freed until no hard references exist from the Root to that object.

A TWeakObjectPtr (weak pointer) allows referencing UObjects without preventing garbage collection. Remember that the referenced object may be collected while you aren’t paying attention, so check IsValid() or Get() != nullptr or you will inadvertently free all the memory as your game crashes to desktop.

3 Likes

Thank you for this explanation Steazy. I finally understand the reasons behind UPROPERTY and TWeakObjectPtr. I have not found a clearer explanation than this.