TArray: question about push, pop, and shrink

Let’s say I have an array that looks like this:

["a", "b", "c", "d"]

Now let’s say I pop an element off the array without shrinking. Would the array now look like this?

["a", "b", "c", null]

And if I pushed an element back onto the array would it then look like this:

["a", "b", "c", null, "d"]

or this:

["a", "b", "c", "d"]

And if there is a null element on the end of an array would pop() try to return the null element or would it return the last valid element?

The functionality I’m looking for is to have an array that stays the same size where I can push and pop elements indefinitely. For ex:

starting array:    `["a", "b", "c", "d"]`
 pop():             `["a", "b", "c", null]`
 pop():             `["a", "b", null, null]`
 push():            `["a", "b", "e", null]`
 push():            `["a", "b", "e", "f"]`

Is that how TArray push and pop works or can I do that with a TArray?

Hey, i’m not that good a C++, but i still had a look at the TArray and its functions. The Pop function seems to take a bool for the “RemoveAt” function. It tells if Shrinking is allowed or not.

ElementType Pop(bool bAllowShrinking=true)
	{
		RangeCheck(0);
		ElementType Result = MoveTemp(GetTypedData()[ArrayNum-1]);
		RemoveAt( ArrayNum-1, 1, bAllowShrinking );
		return Result;
	}

It also seems like i tries to Pop although the Array is empty. So the RangeCheck is doing nothing? I guess the easiest thing to do here would be to code the push and pop yourself. You could just create a static array and the 2 functions to push and pop.

But i don’t want to tell nonsense, so maybe have a look at “Array.h” for the functions.

Normaly the RemoveAt Function should only call the resize if the bool is true:

void RemoveAt( int32 Index, int32 Count=1, bool bAllowShrinking=true )
	{
		CheckInvariants();
		checkSlow((Count >= 0) & (Index >= 0) & (Index+Count <= ArrayNum));

		DestructItems(GetTypedData() + Index, Count);

		// Skip memmove in the common case that there is nothing to move.
		int32 NumToMove = ArrayNum - Index - Count;
		if( NumToMove )
		{
			FMemory::Memmove
			(
				(uint8*)AllocatorInstance.GetAllocation() + (Index      ) * sizeof(ElementType),
				(uint8*)AllocatorInstance.GetAllocation() + (Index+Count) * sizeof(ElementType),
				NumToMove * sizeof(ElementType)
			);
		}
		ArrayNum -= Count;
		
		const int32 NewArrayMax = AllocatorInstance.CalculateSlack(ArrayNum,ArrayMax,sizeof(ElementType));
		if(NewArrayMax != ArrayMax && bAllowShrinking)
		{
			ArrayMax = NewArrayMax;
			AllocatorInstance.ResizeAllocation(ArrayNum,ArrayMax,sizeof(ElementType));
		}
	}

:confused: I hope this information helps you somehow.

I just tested the “Push” function for a resized Array. It won’t fill the NULL, it will keep adding new once, so maybe you really need to write a small function. At least one that loops through the array and checks for the next NULL Element in the array. There are some Function that let you insert Elements at a specific index.

Thanks for the info. That helped answer my questions. I didn’t even think about checking Array.h. Thanks for testing out push() as well. It looks like push() is doing roughly the same thing as add(), so I guess that makes sense.

Hi there!

In order to answer this, we need to understand how TArray (as with many C++ containers) manages its memory.

Let’s start with two concepts - size and capacity. The size of an array is how many elements it has (that is, how many things you’ve put in it). Under the hood, the array probably has capacity for more elements that it currently holds. When the size of the array reaches its capacity, then we need to reallocate the array to a larger heap allocation.

When we talk about shrinking an array, we mean that we want to shrink the capacity of the array. This is generally only desirable when there are tight memory constraints, or where capacity is significantly larger than size, ie, there is a lot of slack.

In UE4’s TArray, the array’s size is accessed through Num() and its capacity is available through Max().

Now to go back to your original question, as eXi states, pushing and popping things from the array will indeed affect its size. We don’t fill the entries with null (especially since we don’t know if null is compatible with the type of the elements).

May I ask why you want have an array of a fixed size that contains invalid entries? Perhaps if I understand your requirements we could come up with a more suitable solution :slight_smile:

I hope that makes sense!

That definitely helps shed some light on TArrays for me. Actually, I probably don’t want null elements in my array. I just wanted the array to stay the same size, because I was trying to avoid reallocating a huge array multiple times every tick. Reading over TArray’s functions I saw:

Reserve()

If I understand this correctly, then I can reserve a certain amount of space and the array will never reallocate itself if I make sure that it doesn’t become bigger than the reserve (along with ensuring bAllowShrinking is false when I pop an element)?

The initial reason I had asked this question is because I thought enemies were trying to shoot null bullets since I couldn’t see their particles. I thought it was due to my lack of knowledge on how TArrays work, but it turns out that was an issue with the bounds of the particle itself.

You’re absolutely right - Reserve() will ensure there is enough capacity for the number of elements specified. Provided you don’t add more than the number of elements you reserved, it’s guaranteed not to re-allocate.

This is quite a common idiom when using any kind of dynamically resizeable container as reallocation is slow. It’s worth noting that TMap and TSet both have Reserve() functionality as well :slight_smile:

Hey Andrew, I’ve asked another question where I talk more about the arrays I’m using and whether I should use some other container for my situation. If you could offer any advice I would appreciate it.

https://answers.unrealengine.com/questions/155386/what-is-a-good-container-to-use-for-a-bullet-manag.html