Non-destructive TArray<AActor*>?

I am periodically scanning actors based on mouse events and keeping a list of actors the user is interacting with.

I tried using a TArray<AActor*> for this, but it has the unintended side effect of destructing the actors when I need to Array.Empty() or Array.Remove(Actor).

Having looked through the TArray source code, it seems like TArray expects that it’s always constructing and destructing anything that it deals with.

Is there something like a TArray that is NOT destructive? I just want a list of pointers. Should I just bypass UE structs and go with C++STL directly?

Sorry if this is a noob question. I am in fact a UE noob. :smile:

Thanks in advance for any insight.

You can do anything with pointers. It’s just a number.
For example, you can convert and store them as Guids:

TArray<FGuid> PtrArray{};
FGuid IntPtr = PtrToGuid(MyActor);

PtrArray.Add(IntPtr);

AActor* ActorPtr = GuidToPtr(PtrArray[0]);

PtrArray.Empty();
FGuid ::PtrToGuid(AActor* Ptr)
{
	FGuid Guid = FGuid::NewGuid();

	if (Ptr==nullptr)
	{
		Guid.Invalidate();
	} else {

		UPTRINT IntPtr = reinterpret_cast<UPTRINT>(Ptr);

		if (sizeof(UPTRINT) > 4)
		{
			Guid[0] ^= (static_cast<uint64>(IntPtr) >> 32);
		}

		Guid[1] ^= IntPtr & 0xFFFFFFFF;
	}

	return Guid;
}

AActor* ::GuidToPtr(const FGuid &Guid)
{
	UPTRINT IntPtr = 0;

	if (sizeof(UPTRINT) > 4)
	{
		IntPtr = static_cast<UPTRINT>(static_cast<uint64>(Guid[0]) << 32);
	}

	IntPtr |= Guid[1] & 0xFFFFFFFF;

	return reinterpret_cast<AActor*>(IntPtr);
}

PtrArray.Empty() in that case will not invoke object destructors.

Clearing items out of an Array doesn’t destroy them, having no references left in the world to them will cause them to be destroyed though.

Thank you both very much for your replies.

Given your input, I’ve taken a fresh look at my code, and I found several problems.

First, I was using TArray.Emplace(AActor*) rather than simply using Add, which perhaps was unintentionally constructing copies of my actors. I never saw these in the world, but in any case I definitely don’t want new actors constructed.

Second, and I think this might have been the real source of the weirdness I was seeing, I was trying to TArray.Remove in the middle of an iterator loop:

TArray<AActor*> ActorArray;
// initialize ActorArray, then:
for (AActor* Actor : ActorArray)
{
    // compute things
    if (sometimes true)
        ActorArray.Remove(Actor); // <-- apparently causes crash
}

TLDR after changing all related code to use TArray.Add rather than Emplace, and changing up that for loop to use an index rather than an iterator, the crashing has stopped.

This code works fine:

TArray<AActor*> ActorArray;
// initialize ActorArray, then:
for (int i = 0; i < ActorArray.Num(); i++)
{
    AActor* Actor = ActorArray[i];
    // compute things
    if (sometimes true)
    {
        if (ActorArray.Remove(Actor) > 0)
            i--;
    }
}

Glad to hear it’s working, but you may encounter issues in the future.

Generally, unless you want your array to affect the lifetime of an object, you should always use TWeakObjectPtr for UObjects or TWeakPtr for non-UObjects. References to raw ptrs (*) shoudn’t prevent GC, but in some circumstances they do so step around that issue using TWeakObjectPtr instead! TWeakObjectPtrs do not affect the lifetime of any UObjects they wrap at all.

TArray<TWeakObjectPtr<AActor>> ActorArray;
AActor* MyActorPtr;

ActorArray.Add(TWeakObectPtr(MyActorPtr));

// If you want to get the raw ptr
AActor* MyActorPtr = ActorArray[ActorIndex].Get();

Other than that, if you want to remove an element from an array you’re iterating on you should use a reverse for loop instead of a forward one or a range-based one:

// Start at last index, iterate backward until and including 0
for (int ActorIndex = ActorArray.Num() - 1; ActorIndex >= 0; ActorIndex--)
{
    // Makes a copy of the Weak ptr because creating a reference (&) wouldn't allow you to remove it
    if (TWeakObjectPtr<AActor> Actor = ActorArray[ActorIndex]; Actor.IsValid())
    {
        ActorArray.RemoveAt(ActorIndex);
    }
}
1 Like

Thanks very much for the follow-up.

Admittedly I haven’t worked in C++ in a number of years. Super fun language with lots of gotchas. :smile:

Your suggestion to use TWeakObjectPtr makes total sense. I will go through and look at my code and make sure I’m using it. Thanks for saving me what I’m sure would have been many hours of banging my head against the wall!

RE removal during iteration, I do see where my code would have broken had it removed multiple items. I’ve edited the post to fix it (I hope!). Why else might it not work after the edit?

It works because Remove does shrink the array, I see you step back after removal so the next iteration would arrive at the next index. Perfectly valid way of doing it, just personally confusing for me to read as a potential team member. If you’re doing it this way rather than in reverse because you want to iterate in order, I’d recommend another approach:

TSet<TWeakPtr<AActor>> ActorsToRemove;

for (TWeakPtr<AActor>& Actor : OriginalArray)
{
    if (bWantToRemove)
    {
        // This should be a ref (&) because the ptr object itself 
        // would be different if we made a copy
        // and therefore calling remove with the copy 
        // object would not remove the original
        ActorsToRemove.Add(Actor);
    }
}

// Again, use a ref (&) to avoid making a copy because copied ptr is not 
// the same as the original and Remove removes an item by its reference.
for (TWeakPtr<AActor>& Actor : ActorsToRemove)
{
    OriginalArray.Remove(Actor);
}

ActorsToRemove.Empty();

Using the above solution probably seems like needless extra steps, but it also allows you to use a range-based iteration on your original array instead of creating a for loop with an index. That means your original array can actually be a set instead, which is much faster to iterate on and remove from than an array. This of course assumes that your original array’s order doesn’t matter. If it does, then use the array.

Got it. Makes sense.

Thank you for the advice!

You’re welcome and good luck out there on the stormy C++ waters!

The best solution is to move the elements to be removed to the end of the array and then remove them in one shot.
Check Algo::RemoveIf

This way, you avoid copying each time you delete an item (this can make a big difference if the array holds type that is heavier than the pointer/int/etc).

You’re right, the Algo library kicks ■■■! But this particular method doesn’t keep items in order less the removals. It’s a great solution, just something to keep in mind if order is important.

In my particular case I do need to keep the items in order.

I’ll definitely check out the Algo library, I haven’t seen it before. Thanks for the tip.

Algo::StableRemoveIf is a bit slow but it also maintains order.

for (FFitnessVrChartItemGroup& Group : ChartItemGroups)
	{
		if (const int32 Result = Algo::StableRemoveIf(Group.ChartItemsInGroup,
                [&InExpiredItems](AFitnessVrChartItemBase* Comparator)
                {
					return InExpiredItems.Contains(Comparator);
                })
            )
		{
			Group.ChartItemsInGroup.SetNum(Result);

			if (!bHaveAnyGroupsChanged)
			{
				bHaveAnyGroupsChanged = true;
			}
		}
	}

StableRemoveIf returns a number of items that match the lambda and will be ‘removed’ by moving the non-matching items to the front of the array. If the result is more than 0, I set the number of elements in the array directly, cutting off the removed items.

No, it doesn’t Emplacing versus adding a simple scalar value like a pointer makes no difference.

Perhaps your TArray was not a UPROPERTY()? If so, the Unreal reflection system won’t know what to do with it, and you can run into ownership problems.

No, it doesn’t cause a crash, unless the object that contains the ActorArray is in itself invalid.
HOWEVER, removing items from an array that you iterate over, will invalidate the iterator, so don’t do that. Using an invalid iterator may or may not cause later undefined behavior, which may or may not lead to a crash.

In general, I would highly suggest stepping through your crashing code in the debugger. Knowing for sure why something crashes, is 100x more helpful than just guessing.

Just a quick note in case you want to do something similar again: If you want to remove items from an array while iterating the array, it’s a good idea to iterate the array in reverse. That way you don’t have to change i every time you remove an element.
For example, this:

TArray<AActor*> ActorArray;
// initialize ActorArray, then:
for (int i = 0; i < ActorArray.Num(); i++)
{
    AActor* Actor = ActorArray[i];
    // compute things
    if (sometimes true)
    {
        if (ActorArray.Remove(Actor) > 0)
            i--;
    }
}

can become this:

TArray<AActor*> ActorArray;
// initialize ActorArray, then:
for (int i = ActorArray.Num() - 1; i >= 0 ; i--)
{
    AActor* Actor = ActorArray[i];
    // compute things
    if (sometimes true)
    {
        ActorArray.Remove(Actor);
    }
}

Sometimes I am really glad I read random threads, because I’ve never heard of this, and now I must know everything about it.

1 Like