This post focuses on pointers in Unreal Engine.

1. Weak vs Soft Pointers

  • A Weak Pointer is created or set to point to an existing UObject that is already instantiated using its GUObjectArray index. The pointer does not need to be a UPROPERTY to know if the object it points to has been garbage collected.
  • A Soft Pointer is a string representation of a path for an object or asset that may or may not be loaded. It stores an additional Weak Pointer internally to keep track of the object after it has been queried and found (in non-editor builds).

Note that regardless of the weak pointers or the soft pointers must point to an UObject, an asset(CDO) is also a UObject.

Soft Pointers

For most of the cases you should use TSoftObjectPtr or TSoftClassPtr.

FSoftObjectPath

Never create a FSoftObjectPath for gameplay code. FSoftObjectPath doesn’t cache the every query will search for its path to get the object again. What’ more, when FSoftObjectPath are used to point to a blueprint class asset, it will always get a UBlueprint class object instread of the generated class(The “_C” generated class).

Used internally by all types of Soft Pointers. Soft Object Path variables can be created in blueprints but should only be done for editor utilities where know what you’re doing, don’t want to point to the UBlueprintGeneratedClass class, and don’t need caching.

If used in blueprints it will only allow picking on-disk assets, not object instances. To point to instances use TSoftObjectPtr.

It isn’t templated. To restrict selection to a type of asset you can use the MetaClass UProperty metadata (or use TSoftClassPtr instead for that kind of functionality).

The pointer is not template, it will not filter any classes

Add a meta proproty for the pointer:

UPROPERTY(EditAnywhere, BlueprintReadOnly, meta=(MetaClass="/Script/Engine.GameModeBase"))
FSoftObjectPath SoftObjectPath;

When objects are being resolved it will search for it using hashes, which has a bit of an overhead compared to normal or weak pointers. Extending classes will usually cache them so once resolved it won’t search again.

FSoftClassPath

Same as FSoftObjectPath but with some helper functions related to loading classes. Mostly a legacy type now. Don’t create your own, use TSoftClassPtr instead.

FSoftObjectPtr

The base class for soft -object and -class pointers. Internally keeps an FSoftObjectPath for finding the object, and a FWeakObjectPtr for caching it after it’s been found.

The following are the members of TPersistentObjectPtr, which is the directly parent of FWeakObjectPtr.

private:

	/** Once the object has been noticed to be loaded, this is set to the object weak pointer **/
	mutable FWeakObjectPtr	WeakPtr;
	/** Compared to CurrentAnnotationTag and if they are not equal, a guid search will be performed **/
	mutable int32			TagAtLastTest;
	/** Guid for the object this pointer points to or will point to. **/
	TObjectID				ObjectID;

Extends TPersistentObjectPtr where a lot of the functionality resides.

Not a USTRUCT so it can’t be used in Blueprints, only C++.

Has two extending types, TSoftObjectPtr or TSoftClassPtr, which provide a templated, BP visible way to either point to an Object Instance or an Asset (CDO) respectively. They should be used instead.

When you call Get() it will call it on the base class which will first check if an internal WeakPtr has been previously cached (in non-PIE sessions), otherwise it will call FSoftObjectPath.ResolveObject to find the object.

/** Synchronously load (if necessary) and return the asset object represented by this asset ptr */
UObject* LoadSynchronous() const
{
	UObject* Asset = Get();
	if (Asset == nullptr && IsPending())
	{
		ToSoftObjectPath().TryLoad();
		
		// TryLoad will have loaded this pointer if it is valid
		Asset = Get();
	}
	return Asset;
}

...
#if WITH_EDITOR
	/** Overridden to deal with PIE lookups */
	FORCEINLINE UObject* Get() const
	{
		if (GPlayInEditorID != INDEX_NONE)
		{
			// Cannot use or set the cached value in PIE as it may affect other PIE instances or the editor
			TWeakObjectPtr<UObject> Result = GetUniqueID().ResolveObject();
			// If this object is pending kill or otherwise invalid, this will return nullptr just like TPersistentObjectPtr<FSoftObjectPath>::Get()
			return Result.Get();
		}
		return TPersistentObjectPtr<FSoftObjectPath>::Get();
	}
#endif

	using TPersistentObjectPtr<FSoftObjectPath>::operator=;
};

TSoftObjectPtr

Templatized wrapper of the generic FSoftObjectPtr. Can be used in Blueprints.

Can be used to point to anything with a path name. Best used to point to things that might not be loaded, whether that’s on-disk assets or objects in any level.

It is the one you can used in blueprint:

Note that because it can point to an object in any level that might not be loaded already it’s very powerful for cross-level communications, not used as much as it should be in my opinion.

in the following example, the TSoftObjectPtr points to an actor which doesn’t been loaded in this level, but loaded in another level.

If you want to reference an already instantiated actor use TWeakObjectPtr instead.

The default value in the editor can’t be set to Blueprint types (use TSoftClassPtr instead). It can point to Data Assets though.

The value can be set either in the Instance Details window by selecting an actor that’s loaded, or even in the Class Defaults for any actor in the currently opened map.

When Get() is called when the pointed-to object isn’t loaded, it will return nullptr. Once loaded it will return the actor with a matching full name and cache it for further queries.

TSoftClassPtr

Templatized wrapper around FSoftObjectPtr that works like a TSubclassOf, it can be used in UProperties for blueprint subclasses.

Used to point to Blueprint types (UBlueprintGeneratedClass) that you can query if they’re loaded or not. Can also be used to load the Class asynchronously (or synchronously, although that introduces hitches).

Doesn’t work for DataAssets though as they shouldn’t be instantiated, use TSoftObjectPtr for those instead.

Weak Pointers

Weak Pointers don’t store path names like Soft Pointers do, they only reference objects that have already been instantiated. The Weak Pointers we are discussing here is not the TWeakPtr, which uses refererce counts for auto GC.

Weak pointers only store two things: int32 ObjectIndex and int32 ObjectSerialNumber.

#if UE_WEAKOBJECTPTR_ZEROINIT_FIX
	int32		ObjectIndex = UE::Core::Private::InvalidWeakObjectIndex;
	int32		ObjectSerialNumber = 0;
#else
	int32		ObjectIndex;
	int32		ObjectSerialNumber;
#endif

When you call Get it will first get the object from GUObjectArray using ObjectIndex, if it exists.

“But ObjectIndex is only 32 bit, doesn’t it easily run out of numbers if you keep spawning and destroying actors” you ask? Excellent question, yes it would in theory, but since you’ll only have a few hundred thousand objects alive at most at any time, those indices get reused.

Because of that we have ObjectSerialNumber to make sure it truly is the object we wanted in case of Index reuse. ObjectSerialNumber is ONLY assigned the first time a weak pointer is pointed at a UObject, otherwise it’s zero. It’s a threadsafe incremental int32 counter.

void FWeakObjectPtr::operator=(const class UObject *Object)
{
	if (Object // && UObjectInitialized() we might need this at some point, but it is a speed hit we would prefer to avoid
		)
	{
		ObjectIndex = GUObjectArray.ObjectToIndex((UObjectBase*)Object);
		ObjectSerialNumber = GUObjectArray.AllocateSerialNumber(ObjectIndex);
		checkSlow(SerialNumbersMatch());
	}
	else
	{
		Reset();
	}
}

“What happens when we run out of those serial numbers”  you ask? That’s where the game fatally asserts (i.e. crashes). That means you’ll be able to make new weak pointers to a maximum of 2,147,483,647 unique UObjects before you run out of serial numbers. It should be way more than enough for most use cases, but take it into account for very long running processes or weird uses of weak pointers.

After comparing the serial numbers it finally checks if the UObject IsValid(), if it isn’t (in case of PendingKill) return null, otherwise return the object. Because of this you can be sure that dereferencing a weak pointer will always return a valid UObject or nullptr if not.

All Delegates keep a reference to their UObjects via a Weak Pointer when using the AddUObject/BindUObject functions, so you can safely subscribe to delegates without worrying about unsubscribing during destruction. This is opposed to using AddRaw/BindRaw functions, which use unmanaged C++ pointers so treat those carefully and try not to use them at all when dealing with UObjects.

To summarize, Weak Pointers are an excellent and safe way of pointing to UObjects with some minor overhead when dereferencing them. Not so weak after all! Because they handle all the internals of fetching and validating the UObjects they also don’t have to be a UPROPERTY

FWeakObjectPtr

A weak pointer to a UObject in memory. Isn’t a USTRUCT so can’t be used in Blueprints, C++ only.

It’s a base class for TWeakObjectPtr, which you should use instead.

TObjectPtr

Templated version of the generic FWeakObjectPtr. Can be used in Blueprints but must be declared in C++.

Many people might not know this but the value can be set in the Instance Details view (not Class Defaults), even though it doesn’t use string representation of the object internally.

Because it doesn’t use string paths it skips the overhead of looking for the asset, but it can only be set to already instantiated objects.

In C++ you can point to any instantiated UObject, no matter which level it’s in, so long as it’s already loaded.

2. Summary

Use these points as you can:

  • TSoftObjectPtr: Used for referencing objects which might or might not be loaded via their path. Can point to actors in other levels even if they aren’t loaded. Can be used with Asynchronous loading functions when pointing to assets (like meshes) to load them ad-hoc. Same as the “Soft Object Reference” blueprint variable type.
  • TSoftClassPtr: Used for referencing class or blueprint types that might or might not be loaded. Once loaded it will give you a class type that you can create instances from. Can be used with Asynchronous loading functions to load them. Same as the “Soft Class Reference” blueprint variable type.
  • TWeakObjectPtr: Used to reference already instantiated objects. Will resolve to null if the object gets destroyed or garbage collected.

Only use these points as a last resort:

  • FSoftObjectPath: Used internally by the other pointer types. Is slow as it doesn’t cache the results. If set in Editor it will point to the UBlueprint class instead of the UBlueprintGeneratedClass, which is usually not what gameplay code wants. Makers of Editor Plugins might want that functionality though.
  • FSoftClassPath: Same as FSoftObjectPath but with some helper functions related to loading classes. Mostly a legacy type now.
  • FSoftObjectPtr: Non-templated and non-BP exposed version of TSoftObjectPtr.

By JiahaoLi

Hypergryph - Game Programmer 2023 - Now Shandong University - Bachelor 2019-2023

One thought on “Soft and Weak Pointers in Unreal Engine 5”

Leave a Reply

Your email address will not be published. Required fields are marked *