La clase contenedora más sencilla de Unreal Engine es TArray. TArray es responsable de la propiedad y organización de una secuencia de otros objetos (llamados «elementos») del mismo tipo. Como una TArray es una secuencia, sus elementos tienen un orden bien definido y sus funciones se usan para manipular de forma determinista esos objetos y su orden.
TArray
TArray es la clase contenedora más común en Unreal Engine. Es rápida, ahorra memoria y es segura. Los tipos de TArray se definen por dos propiedades: el tipo de elemento y un asignador opcional.
El tipo de elemento es el tipo de los objetos que se almacenarán en la matriz. TArray es lo que se denomina un contenedor homogéneo, lo que significa que todos sus elementos son estrictamente del mismo tipo; no es posible almacenar elementos de distintos tipos en una sola TArray.
El asignador se omite con bastante frecuencia y, de forma predeterminada, será el apropiado para la mayoría de los casos de uso. Define cómo se disponen los objetos en la memoria y cómo debería crecer la matriz para dar cabida a más elementos. Hay varios asignadores distintos que puedes usar si decides que el comportamiento predeterminado no es el que necesitas y también puedes escribir el tuyo propio. Hablaremos de esto más adelante.
TArray es un tipo de valor, por lo que debería tratarse como cualquier otro tipo integrado, como un int32 o un float. No está diseñado para ser ampliado y no se recomienda crear o destruir instancias de TArray con new y delete. Los elementos también son tipos de valor y la matriz los posee. La destrucción de una TArray tendrá como resultado la destrucción de cualquier elemento que contenga. Al crear una variable TArray a partir de otra, se copiarán sus elementos en la nueva variable; no hay un estado compartido.
Cómo crear y rellenar una matriz
Para crear una matriz, defínela del siguiente modo:
TArray<int32> IntArray;Esto crea una matriz vacía diseñada para contener una secuencia de enteros. El tipo de elemento puede ser cualquier tipo de valor que se pueda copiar y destruir de acuerdo con las reglas de valores normales de C++, como int32, FString, TSharedPtr, etc. No se ha especificado ningún asignador, por lo que el TArray usará el asignador predeterminado basado en el montón. En este punto, no se ha asignado ninguna memoria.
TArray (al igual que muchos contenedores de Unreal Engine) asume que el tipo de elemento es reubicable de forma trivial, lo que significa que los elementos se pueden con seguridad de una posición a otra en la memoria copiando directamente bytes sin procesar.
Las TArrays se pueden rellenar de varias maneras. Una forma es con la función Init, que rellenará una matriz con un número determinado de copias de un elemento:
IntArray.Init(10, 5);
// IntArray == [10,10,10,10,10]Las funciones Add y Emplace pueden crear nuevos elementos al final de la matriz:
TArray<FString> StrArr;
StrArr.Add (TEXT("Hello"));
StrArr.Emplace(TEXT("World"));
// StrArr == ["Hello","World"]El asignador de la matriz proporciona memoria según sea necesario cada vez que se añaden nuevos elementos a la matriz. El asignador predeterminado añade suficiente memoria para múltiples elementos nuevos cada vez que se excede el tamaño actual de la matriz. Add y Emplace hacen prácticamente lo mismo, pero con una sutil diferencia:
Add(oPush) copiará (o moverá) una instancia del tipo de elemento a la matriz.Emplaceusará los argumentos que le des para construir una nueva instancia del tipo de elemento.
En el caso de nuestra TArray<FString>, Add creará una FString temporal a partir del literal de cadena y luego moverá el contenido de esa FString temporal a una nueva FString dentro del contenedor, mientras que Emplace solo creará la nueva FString directamente usando el literal de cadena. El resultado final es el mismo, pero con Emplace se evita crear una variable temporal, lo que suele no ser deseable para tipos de valor no triviales como FString.
En general, es preferible usar Emplace a Add, ya que evita la creación de variables temporales innecesarias en el lugar de la llamada que luego se copian o mueven al contenedor. Como regla general, usa Add para tipos triviales y Emplace en caso contrario. Emplace nunca será menos eficiente que Add, pero Add puede leerse mejor.
Append añade varios elementos a la vez de otra TArray o de un puntero a una matriz C normal y del tamaño de esa matriz:
FString Arr[] = { TEXT("of"), TEXT("Tomorrow") };
StrArr.Append(Arr, ARRAY_COUNT(Arr));
// StrArr == ["Hello","World","of","Tomorrow"]AddUnique solo añade un nuevo elemento al contenedor si no existe ya un elemento equivalente. La equivalencia se comprueba usando el operator== del tipo de elemento:
StrArr.AddUnique(TEXT("!"));
// StrArr == ["Hello","World","of","Tomorrow","!"]
StrArr.AddUnique(TEXT("!"));
// StrArr is unchanged as "!" is already an elementInsert, al igual que Add, Emplace y Append, añade un único elemento o una copia de una matriz de elementos en un índice dado:
StrArr.Insert(TEXT("Brave"), 1);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]La función SetNum puede establecer directamente el número de elementos de la matriz. Los nuevos elementos se crearán usando el constructor predeterminado del tipo de elemento si el nuevo número es mayor que el actual:
StrArr.SetNum(8);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!","",""]SetNum también eliminará elementos si el nuevo número es menor que el actual. Más adelante encontrarás información detallada sobre la eliminación de elementos:
StrArr.SetNum(6);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]Iteración
Hay varias formas de iterar sobre los elementos de tu matriz, pero la más recomendable es usar la función de C++:
FString JoinedStr;
for (auto& Str : StrArr)
{
JoinedStr += Str;
JoinedStr += TEXT(" ");
}
// JoinedStr == "Hello Brave World of Tomorrow ! "Por supuesto, también es posible la iteración basada en índices:
for (int32 Index = 0; Index != StrArr.Num(); ++Index)
{
JoinedStr += StrArr[Index];
JoinedStr += TEXT(" ");
}Por último, las matrices también tienen su propio tipo de iterador para tener más control sobre tu iteración. Hay dos funciones llamadas CreateIterator y CreateConstIterator que puedes usar para tener acceso de lectura-escritura o de solo lectura a los elementos respectivamente:
for (auto It = StrArr.CreateConstIterator(); It; ++It)
{
JoinedStr += *It;
JoinedStr += TEXT(" ");
}Ordenación
Las matrices se pueden ordenar simplemente llamando a la función Sort:
StrArr.Sort();
// StrArr == ["!","Brave","Hello","of","Tomorrow","World"]Aquí, los valores se ordenan según el operator< del tipo de elemento. En el caso de FString, se trata de una comparación lexicográfica que no distingue entre mayúsculas y minúsculas. También se puede implementar un predicado binario para proporcionar distintas semánticas de ordenación, como esta:
StrArr.Sort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Hello","Brave","World","Tomorrow"]Ahora las cadenas están ordenadas por sus longitudes. Fíjate en cómo las tres cadenas de la misma longitud («Hello», «Brave» y «World») han cambiado de orden en relación con sus posiciones anteriores en la matriz. Esto se debe a que Sort es inestable y no se garantiza el orden relativo de los elementos equivalentes (esas cadenas son equivalentes aquí porque el predicado solo compara la longitud). Sort se implementa como una ordenación rápida.
La función HeapSort, con predicado binario o sin él, se puede usar para realizar una ordenación del montón. Que la uses o no depende de tus datos concretos y de la eficiencia con la que se ordenan en comparación con la función Sort. Al igual que Sort, HeapSort no es estable. Si hubiéramos usado HeapSort en lugar de Sort, este sería el resultado (el mismo en este caso):
StrArr.HeapSort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Hello","Brave","World","Tomorrow"]Por último, puedes usar StableSort para garantizar el orden relativo de los elementos equivalentes después de ordenarlos. Si antes hubiéramos llamado a StableSort en lugar de Sort o HeapSort, el resultado habría sido el siguiente:
StrArr.StableSort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Brave","Hello","World","Tomorrow"]Es decir, «Brave», «Hello» y «World» permanecen en su mismo orden relativo después de haber sido ordenados lexicográficamente. StableSort se implementa como una ordenación por combinación.
Consultas
Podemos preguntar a la matriz cuántos elementos contiene usando la función Num:
int32 Count = StrArr.Num();
// Count == 6Si necesitas acceder directamente a la memoria de la matriz, por ejemplo para interoperar con una API de estilo C, puedes usar la función GetData para que devuelva un puntero a los elementos de la matriz. Este puntero solo es válido mientras exista la matriz y antes de que se realice cualquier operación de mutación en la matriz. Solo se pueden desreferenciar los primeros Num índices de StrPtr:
FString* StrPtr = StrArr.GetData();
// StrPtr[0] == "!"
// StrPtr[1] == "of"
// ...
// StrPtr[5] == "Tomorrow"
// StrPtr[6] - undefined behaviorSi el contenedor es constante, el puntero devuelto también será constante.
También puedes preguntar al contenedor el tamaño de los elementos:
uint32 ElementSize = StrArr.GetTypeSize();
// ElementSize == sizeof(FString)Para recuperar elementos, puedes usar el operator[] de indexación y pasarle un índice de base cero al elemento que quieras:
FString Elem1 = StrArr[1];
// Elem1 == "of"Pasar un índice no válido (menor que 0 o mayor o igual que Num()) provocará un error en tiempo de ejecución. Puedes preguntar al contenedor si un índice en particular es válido usando la función IsValidIndex:
bool bValidM1 = StrArr.IsValidIndex(-1);
bool bValid0 = StrArr.IsValidIndex(0);
bool bValid5 = StrArr.IsValidIndex(5);
bool bValid6 = StrArr.IsValidIndex(6);
// bValidM1 == false
// bValid0 == true
// bValid5 == true
// bValid6 == falseEl operator[] devuelve una referencia, por lo que también puede usarse para mutar los elementos dentro de la matriz, suponiendo que tu matriz no sea constante:
StrArr[3] = StrArr[3].ToUpper();
// StrArr == ["!","of","Brave","HELLO","World","Tomorrow"]Al igual que la función GetData, el operator[] devolverá una referencia constante si la matriz es constante. También puedes indexar desde el final de la matriz hacia atrás con la función Last. De forma predeterminada, el índice es cero. La función Top es un sinónimo de Last, solo que no necesita un índice:
FString ElemEnd = StrArr.Last();
FString ElemEnd0 = StrArr.Last(0);
FString ElemEnd1 = StrArr.Last(1);
FString ElemTop = StrArr.Top();
// ElemEnd == "Tomorrow"
// ElemEnd0 == "Tomorrow"
// ElemEnd1 == "World"
// ElemTop == "Tomorrow"Podemos preguntar a la matriz si contiene un cierto elemento:
bool bHello = StrArr.Contains(TEXT("Hello"));
bool bGoodbye = StrArr.Contains(TEXT("Goodbye"));
// bHello == true
// bGoodbye == falseO preguntar a la matriz si contiene un elemento que coincida con un predicado específico:
bool bLen5 = StrArr.ContainsByPredicate([](const FString& Str){
return Str.Len() == 5;
});
bool bLen6 = StrArr.ContainsByPredicate([](const FString& Str){
return Str.Len() == 6;
});
// bLen5 == true
// bLen6 == falsePodemos encontrar elementos usando la familia de funciones Find. Para comprobar si un elemento existe y devolver su índice, usamos Find:
int32 Index;
if (StrArr.Find(TEXT("Hello"), Index))
{
// Index == 3
}Esto establece que Index sea el índice del primer elemento encontrado. Si hay elementos duplicados y queremos encontrar el índice del último elemento, usaremos la función FindLast:
int32 IndexLast;
if (StrArr.FindLast(TEXT("Hello"), IndexLast))
{
// IndexLast == 3, because there aren't any duplicates
}Ambas funciones devuelven un booleano para indicar si se ha encontrado o no un elemento, además de escribir el índice de ese elemento en una variable cuando se encuentra.
Find y FindLast también pueden devolver directamente el índice de un elemento. Harán esto si no les pasas el índice como argumento explícito. Puede ser más breve que la función anterior. La función que uses dependerá de lo que se adapte a tus necesidades o estilo.
Si no se ha encontrado ningún elemento, se devuelve el valor especial INDEX_NONE:
int32 Index2 = StrArr.Find(TEXT("Hello"));
int32 IndexLast2 = StrArr.FindLast(TEXT("Hello"));
int32 IndexNone = StrArr.Find(TEXT("None"));
// Index2 == 3
// IndexLast2 == 3
// IndexNone == INDEX_NONEIndexOfByKey funciona de forma similar, pero permite comparar los elementos con un objeto arbitrario. Con las funciones Find, el argumento se convierte al tipo de elemento (FString en este caso) antes de que empiece la búsqueda. Con IndexOfByKey, la clave se compara directamente, lo que permite realizar búsquedas incluso cuando el tipo de clave no se puede convertir directamente al tipo de elemento.
IndexOfByKey funciona con cualquier tipo de clave para la que exista operator==(ElementType, KeyType). IndexOfByKey devolverá el índice del primer elemento encontrado o INDEX_NONE si no se ha encontrado ningún elemento:
int32 Index = StrArr.IndexOfByKey(TEXT("Hello"));
// Index == 3La función IndexOfByPredicate encuentra el índice del primer elemento que coincide con el predicado especificado, devolviendo de nuevo el valor especial INDEX_NONE si no se encuentra ninguno:
int32 Index = StrArr.IndexOfByPredicate([](const FString& Str){
return Str.Contains(TEXT("r"));
});
// Index == 2En lugar de devolver índices, podemos devolver punteros a los elementos que encontramos. FindByKey funciona como IndexOfByKey, comparando los elementos con un objeto arbitrario, pero devolviendo un puntero al elemento que encuentra. Si no encuentra un elemento, devolverá nullptr:
auto* OfPtr = StrArr.FindByKey(TEXT("of")));
auto* ThePtr = StrArr.FindByKey(TEXT("the")));
// OfPtr == &StrArr[1]
// ThePtr == nullptrFindByPredicate se puede usar como IndexOfByPredicate, solo que devuelve un puntero en lugar de un índice:
auto* Len5Ptr = StrArr.FindByPredicate([](const FString& Str){
return Str.Len() == 5;
});
auto* Len6Ptr = StrArr.FindByPredicate([](const FString& Str){
return Str.Len() == 6;
});
// Len5Ptr == &StrArr[2]
// Len6Ptr == nullptrPor último, puedes recuperar una matriz de elementos que coincidan con un predicado concreto con la función FilterByPredicate:
auto Filter = StrArray.FilterByPredicate([](const FString& Str){
return !Str.IsEmpty() && Str[0] < TEXT('M');
});Eliminación
Puedes borrar elementos de la matriz con la familia de funciones Remove. La función Remove elimina todos los elementos que se consideran iguales al elemento que proporcionas, de acuerdo con la función operator== del tipo de elemento. Por ejemplo:
TArray<int32> ValArr;
int32 Temp[] = { 10, 20, 30, 5, 10, 15, 20, 25, 30 };
ValArr.Append(Temp, ARRAY_COUNT(Temp));
// ValArr == [10,20,30,5,10,15,20,25,30]
ValArr.Remove(20);
// ValArr == [10,30,5,10,15,25,30]También puedes usar RemoveSingle para borrar el primer elemento coincidente de la matriz. Esto es útil si sabes que tu matriz puede contener duplicados y solo quieres borrar uno, o como una optimización si sabes que tu matriz solo puede contener un elemento coincidente:
ValArr.RemoveSingle(30);
// ValArr == [10,5,10,15,25,30]También podemos eliminar elementos por su índice de base cero usando la función RemoveAt. Puedes que quieras usar IsValidIndex para verificar que la matriz tiene un elemento con el índice que planeas proporcionar, ya que pasar un índice no válido a esta función provocará un error en tiempo de ejecución.
ValArr.RemoveAt(2); // Removes the element at index 2
// ValArr == [10,5,15,25,30]
ValArr.RemoveAt(99); // This will cause a runtime error as
// there is no element at index 99También podemos eliminar elementos que coincidan con un predicado usando la función RemoveAll. Por ejemplo, al eliminar todos los valores que son múltiplos de 3:
ValArr.RemoveAll([](int32 Val) {
return Val % 3 == 0;
});
// ValArr == [10,5,25]En todos estos casos, cuando se eliminaron elementos, los que le seguían se aleatorizaron en índices más bajos, ya que nunca puede quedar huecos en la matriz.
El proceso de aleatorización tiene una sobrecarga. Si realmente no te importa el orden en el que quedan los elementos restantes, esta sobrecarga puede reducirse usando las funciones RemoveSwap, RemoveAtSwap y RemoveAllSwap, que funcionan como sus variantes sin intercambio, excepto que no garantizan el orden de los elementos restantes, lo que les permite completar sus tareas más rápidamente:
TArray<int32> ValArr2;
for (int32 i = 0; i != 10; ++i)
ValArr2.Add(i % 5);
// ValArr2 == [0,1,2,3,4,0,1,2,3,4]
ValArr2.RemoveSwap(2);
// ValArr2 == [0,1,4,3,4,0,1,3]
ValArr2.RemoveAtSwap(1);
// ValArr2 == [0,3,4,3,4,0,1]
Por último, la función Empty eliminará todo lo que haya en la matriz:
ValArr2.Empty();
// ValArr2 == []Operadores
Las matrices son tipos de valor normales y, como tales, pueden copiarse mediante el constructor de copia estándar o el operador de asignación. Como las matrices son estrictamente propietarias de sus elementos, copiar una matriz es profunda, por lo que la nueva matriz tendrá su propia copia de los elementos:
TArray<int32> ValArr3;
ValArr3.Add(1);
ValArr3.Add(2);
ValArr3.Add(3);
auto ValArr4 = ValArr3;
// ValArr4 == [1,2,3];
ValArr4[0] = 5;
// ValArr3 == [1,2,3];
// ValArr4 == [5,2,3];Como alternativa a la función Append, puedes concatenar matrices con el operator+=:
ValArr4 += ValArr3;
// ValArr4 == [5,2,3,1,2,3]TArray también es compatible con la semántica de movimientos, la cual puede invocarse con la función MoveTemp. Después de un movimiento, se garantiza que la matriz de origen quedará vacía:
ValArr3 = MoveTemp(ValArr4);
// ValArr3 == [5,2,3,1,2,3]
// ValArr4 == []Las matrices se pueden comparar con operator== y operator!=. El orden de los elementos es importante. Dos matrices solo son iguales si tienen la misma cantidad de elementos en el mismo orden. Los elementos se comparan usando su propio operator==:
TArray<FString> FlavorArr1;
FlavorArr1.Emplace(TEXT("Chocolate"));
FlavorArr1.Emplace(TEXT("Vanilla"));
// FlavorArr1 == ["Chocolate","Vanilla"]
auto FlavorArr2 = Str1Array;
// FlavorArr2 == ["Chocolate","Vanilla"]
bool bComparison1 = FlavorArr1 == FlavorArr2;
// bComparison1 == true
Montón
TArray tiene funciones que son compatibles con una estructura de datos de montón binario. Un montón es un tipo de árbol binario en el que cualquier nodo padre es equivalente o se ordena antes que todos sus nodos hijo. Cuando se implementa como una matriz, el nodo raíz del árbol está en el elemento 0 y los índices de los nodos hijo izquierdo y derecho de un nodo en el índice N son 2N+1 y 2N+2 respectivamente. Los hijos no se encuentran en ningún orden en particular entre sí.
Cualquier matriz existente puede convertirse en un montón llamando a la función Heapify. Esto está sobrecargado para tomar un predicado o no, donde la versión no predicada usará el operator< del tipo de elemento para determinar el orden:
TArray<int32> HeapArr;
for (int32 Val = 10; Val != 0; --Val)
{
HeapArr.Add(Val);
}
// HeapArr == [10,9,8,7,6,5,4,3,2,1]
HeapArr.Heapify();
// HeapArr == [1,2,4,3,6,5,8,10,7,9]Esta es una visualización del árbol:
Los nodos del árbol se pueden leer de izquierda a derecha y de arriba abajo según el orden de los elementos de la matriz apilada. Ten en cuenta que la matriz no está necesariamente ordenada después de transformarse en un montón. Aunque una matriz ordenada también sería un montón válido, la definición de la estructura del montón es lo suficientemente flexible como para permitir varios montones válidos para el mismo conjunto de elementos.
Se pueden añadir nuevos elementos al montón a través de la función HeapPush, reordenando otros nodos para mantener el montón:
HeapArr.HeapPush(4);
// HeapArr == [1,2,4,3,4,5,8,10,7,9,6]Las funciones HeapPop y HeapPopDiscard se usan para eliminar el nodo superior del montón. La diferencia entre los dos es que el primero toma una referencia a un tipo de elemento para devolver una copia del elemento superior, mientras que el segundo simplemente elimina el nodo superior sin devolverlo de ninguna manera. Ambas funciones producen el mismo cambio en la matriz, y el montón se mantiene de nuevo reordenando otros elementos adecuadamente:
int32 TopNode;
HeapArr.HeapPop(TopNode);
// TopNode == 1
// HeapArr == [2,3,4,6,4,5,8,10,7,9]HeapRemoveAt eliminará un elemento de la matriz en un índice dado y luego reordenará los elementos para mantener el montón:
HeapArr.HeapRemoveAt(1);
// HeapArr == [2,4,4,6,9,5,8,10,7]Solo se deben llamar a HeapPush, HeapPop, HeapPopDiscard y HeapRemoveAt cuando la estructura ya sea un montón válido, como después de una llamada a Heapify, cualquier otra operación de montón o manipulando manualmente la matriz en un montón.
Cada una de estas funciones, incluida Heapify, puede tomar un predicado binario opcional para determinar el orden de los elementos nodo en el montón. De forma predeterminada, las operaciones del montón usan el operator< del tipo de elemento para determinar el orden. Al usar un predicado personalizado, es importante usar el mismo predicado en todas las operaciones del montón.
Por último, el nodo superior del montón se puede inspeccionar con HeapTop, sin cambiar la matriz:
int32 Top = HeapArr.HeapTop();
// Top == 2Holgura
Dado que las matrices pueden cambiar de tamaño, usan una cantidad variable de memoria. Para evitar una reasignación cada vez que se añaden elementos, los asignadores suelen proporcionar más memoria de la solicitada para que futuras llamadas a Add no supongan una penalización en el rendimiento por la reasignación. Asimismo, eliminar elementos no suele liberar memoria. Esto deja la matriz con elementos de holgura, que son espacios de almacenamiento de elementos preasignados que no están en uso. La cantidad de holgura en una matriz se define como la diferencia entre el número de elementos almacenados en la matriz y el número de elementos que la matriz podría almacenar con la cantidad de memoria que tiene asignada.
Como una matriz construida por defecto no asigna memoria, inicialmente la holgura será cero. Puedes averiguar cuánta holgura hay en una matriz usando la función GetSlack. La función Max puede obtener el número máximo de elementos que puede contener la matriz antes de que el contenedor se reajuste. GetSlack equivale a la diferencia entre Max y Num:
TArray<int32> SlackArray;
// SlackArray.GetSlack() == 0
// SlackArray.Num() == 0
// SlackArray.Max() == 0
SlackArray.Add(1);
// SlackArray.GetSlack() == 3
// SlackArray.Num() == 1
// SlackArray.Max() == 4
La cantidad de holgura que hay en un contenedor después de una reasignación la decide el asignador, por lo que los usuarios no deberíais depender de que la holgura permanezca constante.
Aunque no es necesaria la gestión de la holgura, puedes aprovecharla para dar pistas de optimización de matriz. Por ejemplo, si sabes que estás a punto de añadir 100 nuevos elementos a la matriz, puedes garantizar que tienes una holgura de al menos 100 antes de añadirlos, para que la matriz no necesite asignar memoria mientras añade los nuevos elementos. La función Empty, mencionada anteriormente, recibe un argumento de holgura opcional:
SlackArray.Empty();
// SlackArray.GetSlack() == 0
// SlackArray.Num() == 0
// SlackArray.Max() == 0
SlackArray.Empty(3);
// SlackArray.GetSlack() == 3
// SlackArray.Num() == 0
// SlackArray.Max() == 3
SlackArray.Add(1);
SlackArray.Add(2);
Hay una función Reset que funciona de forma similar a Empty, excepto que no libera memoria si la asignación actual ya proporciona la holgura solicitada. Sin embargo, asignará más memoria si la holgura solicitada es mayor:
SlackArray.Reset(0);
// SlackArray.GetSlack() == 3
// SlackArray.Num() == 0
// SlackArray.Max() == 3
SlackArray.Reset(10);
// SlackArray.GetSlack() == 10
// SlackArray.Num() == 0
// SlackArray.Max() == 10Y, por último, puedes eliminar toda la holgura con la función Shrink, que redimensionará la asignación al tamaño mínimo necesario para contener los elementos actuales. Shrink no tiene ningún efecto sobre los elementos de la matriz:
SlackArray.Add(5);
SlackArray.Add(10);
SlackArray.Add(15);
SlackArray.Add(20);
// SlackArray.GetSlack() == 6
// SlackArray.Num() == 4
// SlackArray.Max() == 10
SlackArray.Shrink();
// SlackArray.GetSlack() == 0
// SlackArray.Num() == 4
Memoria sin procesar
En última instancia, TArray no es más que un contenedor de la memoria asignada. Puede ser útil tratarlo como tal modificando directamente los bytes de la asignación y creando elementos por tu cuenta. TArray siempre intentará hacer lo mejor que pueda con la información que tiene, pero a veces puede que tengas que bajar a un nivel inferior.
Las siguientes funciones te dan acceso rápido y de bajo nivel a TArray y a los datos que contiene, pero, si no se usan correctamente, pueden poner el contenedor en estados no válidos y provocar un comportamiento indefinido. Depende de ti devolver el contenedor a un estado válido después de invocar estas funciones, pero antes de que se llame a cualquier otra función regular.
Las funciones AddUninitialized e InsertUninitialized añadirán espacio sin inicializar a la matriz. Funcionan como Add e Insert, respectivamente, pero no llamarán al constructor del tipo de elemento. Esto puede ser útil para evitar llamar a constructores. Podrías hacerlo en casos como el siguiente ejemplo, donde pretendas sobrescribir toda la estructura con una llamada a Memcpy:
int32 SrcInts[] = { 2, 3, 5, 7 };
TArray<int32> UninitInts;
UninitInts.AddUninitialized(4);
FMemory::Memcpy(UninitInts.GetData(), SrcInts, 4*sizeof(int32));
// UninitInts == [2,3,5,7]También puedes usar esta función para reservar memoria para objetos que quieras construir por tu cuenta:
TArray<FString> UninitStrs;
UninitStrs.Emplace(TEXT("A"));
UninitStrs.Emplace(TEXT("D"));
UninitStrs.InsertUninitialized(1, 2);
new ((void*)(UninitStrs.GetData() + 1)) FString(TEXT("B"));
new ((void*)(UninitStrs.GetData() + 2)) FString(TEXT("C"));
// UninitStrs == ["A","B","C","D"]AddZeroed e InsertZeroed funcionan de manera similar, excepto que también ponen a cero los bytes del espacio añadido/insertado:
struct S
{
S(int32 InInt, void* InPtr, float InFlt)
: Int(InInt)
, Ptr(InPtr)
, Flt(InFlt)
{
}
int32 Int;
void* Ptr;
También están las funciones SetNumUninitialized y SetNumZeroed, que son como SetNum, salvo que, en caso de que el nuevo número sea mayor que el actual, el espacio para los nuevos elementos se dejará sin inicializar o se pondrá a cero bit a bit, respectivamente. Al igual que con las funciones AddUninitialized e InsertUninitialized, debes garantizar que, si es necesario, los nuevos elementos se construyan correctamente en el nuevo espacio si es necesario:
SArr.SetNumUninitialized(3);
new ((void*)(SArr.GetData() + 1)) S(5, (void*)0x12345678, 3.14);
new ((void*)(SArr.GetData() + 2)) S(2, (void*)0x87654321, 2.72);
// SArr == [
// { Int: 0, Ptr: nullptr, Flt: 0.0f },
// { Int: 5, Ptr: 0x12345678, Flt: 3.14f },
// { Int: 2, Ptr: 0x87654321, Flt: 2.72f }
// ]
SArr.SetNumZeroed(5);
Usa las familias de funciones «Uninitialized» y «Zeroed» con cuidado. Si un tipo de elemento incluye un miembro que necesita construcción, o que no tiene un estado a cero válido, puede generar elementos de matriz no válidos y un comportamiento indefinido. Estas funciones son especialmente útiles en matrices de tipos que probablemente no cambien nunca, como FMatrix o FVector.
Varios
La función BulkSerialize es una función de serialización que puede usarse como operator<< alternativo para serializar la matriz como un bloque de bytes sin procesar, en lugar de hacer una serialización por elemento. Esto puede mejorar el rendimiento con elementos triviales, como un tipo integrado o una estructura de datos.
Las funciones CountBytes y GetAllocatedSize se usan para calcular cuánta memoria está utilizando la matriz en ese momento. CountBytes toma un FArchive, mientras que a GetAllocatedSize se le puede llamar directamente. Estas funciones se suelen usar para generar informes de estadísticas.
Las funciones Swap y SwapMemory toman dos índices e intercambian el valor de los elementos en esos índices. Son equivalentes, excepto que Swap realiza una comprobación adicional de errores en los índices y afirmará si los índices están fuera de intervalo.