What's the difference between using TWeakObjectPtr or using UObject*

What’s the difference between using

TWeakObjectPtr<UObject> WeakPointer;
WeakPointer.IsValid();

or using

UObject *Obj;
Obj->IsValidLowLevel();
1 Like

Hi ,

Both of the options that you mentioned perform the same essential function. However, in general TWeakObjectPtr will be the better choice if you do not need your reference to be a UPROPERTY, since it is built specifically to use with weak pointers. Just keep in mind that with a weak pointer you will never be sure if the reference is still valid, so you will always need to check it before using it.

Dear ,

#There’s a Huge Difference, One Will Always Crash

There’s a huge game-breaking difference between your two examples!


This will never crash

    TWeakObjectPtr<UObject> WeakPointer;
   if( WeakPointer.IsValid()) {}

The below code is guaranteed to crash every single time based on how you’ve written in it

UObject *Obj;
if(Obj->IsValidLowLevel()){}

reason = you are de-referencing a pointer that is pointing to nothing (dereference is the operator)

#IsValidLowLevel()

IsValidLowLevel is not meant to be used for verifying the pointer, it is for verifying the integrity of the UObject after it is already known to exist, but might have been deleted recently. See below for how you can verify the pointer itself.

#Solution
If using raw c++ pointers, you can just do this

UObject* Obj;
if(Obj != nullptr)
{
  //Safe!
  Obj->GetName();
}

The above using raw C++ pointers, and the example using .IsValid() are the two cases that are equivalent, and both are safe.

#Why is this such a big deal?

Whether you understand this difference properly is what makes your game crash to desktop constantly, or run stable all the time (minus any infinite while loops hee hee) :slight_smile:

6 Likes

Thanks a lot!

Did you read my reply below? you are missing some important information

One of your examples is safe, the other can easily crash your game!

's answer below provides a good technical explanation of the difference between these two options, and why TWeakObjectPtr is almost always going to be the better option. Regardless of which option you choose to use, always make sure to check your weak pointers before using them or you will find yourself running into crashes when testing your game.

I appreciate the compliment !

But there is something important that I really need to have cleared up

You wrote

“Both of the options that you mentioned perform the same essential function.”

This is not true!

.IsValid() is for checking the validity of a sharedptr,

IsValidLowLevel() has the same “theme” of functionality, but is a much deeper verifying of the actual integrity of an existing UObject whose pointer is already known to be valid.

IsValidLowLevel() is so different from .IsValid(), that IsValidLowLevel() actually depends on the pointer being valid, and if it is not, IsValidLowLevel() will crash the person’s game.

I just need this to be clearly understood, it is actually a very critical difference that could crash people’s games to desktop if not understood properly.


An Example

If you look in Class.cpp

// Avoid duplicate entries.
if ( Object != NULL && !ObjectArray.Contains(Object) )
{
  check( Object->IsValidLowLevel() );
  ObjectArray.Add( Object );
}

The first check, checking pointer validity, is the != NULL check

The Object->IsValidLowLevel() check is a separate test that can only be safely done once the pointer is verified to be valid.

The .IsValid() check of shared ptrs is actually equivalent to the first check, the != NULL check

Even if someone does the.IsValid() check, they still need to do the next check for IsValidLowLevel() to verify internal UObject integrity.

So these are two distinct levels of verifying the data, and one depends on the other

I hope my tone is clear, I am just trying to convey an important point, and that is all :slight_smile:

I have had cases in my own game where it would crash even if the .IsValid() check was performed, but not the IsValidLowLevel() check.

They are distinct and both important :slight_smile:


I appreciate you , and all that you do, I would not even be bothering unless this was a crash-level important matter :slight_smile:

2 Likes

The question is a bit ambiguous, sorry about that. The first line’s intention was just to show the type. The snippets weren’t meant to be executed directly. Thanks for explaining IsValidLowLevel in more detail.

Great to hear from you !

:slight_smile:

I though you were safe after calling IsValid. In what cases do you need to call IsValidLowLevel, e.g. when can the UObject become invalid. Thanks a lot for explaining this!

In my in-game level editor, I have had cases where .IsValid() passed, but if I tried to use the UObject the game would crash, because the UObject was in the process of being deleted.

If the destroying of an object has already begun, but not finished, its pointer can still be valid, but the integrity of the UObject data has been compromised.

So in my case it involved UObjects that had been recently deleted, and I was still trying to use them every tick, or access their data them in some way.

It was only when I added the IsValidLowLevel() check, in addition to verifying the pointer, that my game stopped crashing.

My game would not be functional right now if I did not find out about IsValidLowLevel(), as distinct from simply verifying the pointer

Example:

if(SomeObject.IsValid())
{
  if(SomeObject->IsValidLowLevel()) //if not included, game sometimes crashed
 {
    //Actually do stuff with SomeObject
 }
}

My specif case was UMaterialInstanceDynamic’s that I was periodically deleting, but my HUD class was checking them every tick using only the simple pointer validity test.

Adding the additional check of IsValidLowLevel() completely removed those periodic crashes and was a make-or-break moment for my project :slight_smile:

I had crash call stacks showing me down to the line where it was happening, AFTER the IsValid() check had passed successfully!

1 Like

Okay, I understand what you mean now =) Thanks a lot for your effort explaining this!

Sorry for the necro, but I’ve been using both normal object pointers (*) and TweakObjectPtrs for a while now, and I’ve been getting some really strange crashes when using TWeakObjectPtrs… I always check that the pointer is valid before de-referencing it, as says, but that’s not enough! It still crashes with a “read access violation”. When I switch to using a normal object pointer the crashes seem to go away? Has anyone else experienced this?

@

I suppose it is different now ?

/**  
 * Test if this points to a live UObject
 * @param bEvenIfPendingKill, if this is true, pendingkill objects are considered valid
 * @param bThreadsafeTest, if true then function will just give you information whether referenced 
 *							UObject is gone forever (@return false) or if it is still there (@return true, no object flags checked).
 * @return true if Get() would return a valid non-null pointer
**/
FORCEINLINE bool IsValid(bool bEvenIfPendingKill, bool bThreadsafeTest = false) const
{
	return TWeakObjectPtrBase::IsValid(bEvenIfPendingKill, bThreadsafeTest);
}


/**
 * Test if this points to a live UObject. This is an optimized version implying bEvenIfPendingKill=false, bThreadsafeTest=false.
 * @return true if Get() would return a valid non-null pointer
 */
FORCEINLINE bool IsValid(/*bool bEvenIfPendingKill = false, bool bThreadsafeTest = false*/) const
{
	return TWeakObjectPtrBase::IsValid();
}

From the comments above of the engine code it also checks if PendingKill

Which leads to this Function:

/** Private (inlined) version for internal use only. */
FORCEINLINE_DEBUGGABLE bool Internal_IsValid(bool bEvenIfPendingKill, bool bThreadsafeTest) const
{
	if (ObjectSerialNumber == 0)
	{
		checkSlow(ObjectIndex == 0 || ObjectIndex == -1); // otherwise this is a corrupted weak pointer
		return false;
	}
	if (ObjectIndex < 0)
	{
		return false;
	}
	FUObjectItem* ObjectItem = GUObjectArray.IndexToObject(ObjectIndex);
	if (!ObjectItem)
	{
		return false;
	}
	if (!SerialNumbersMatch(ObjectItem))
	{
		return false;
	}
	if (bThreadsafeTest)
	{
		return true;
	}
	return GUObjectArray.IsValid(ObjectItem, bEvenIfPendingKill);
}