La TArray est la classe de conteneur la plus simple de l'Unreal Engine. La TArray est responsable de la gestion et de l'organisation d'une séquence d'autres objets (appelés "éléments") du même type. Une TArray étant une séquence, ses éléments ont un ordre bien défini et ses fonctions permettent de manipuler de manière déterministe ces objets et leur ordre.
TArray
TArray est la classe de conteneur la plus courante dans l'Unreal Engine. Elle est rapide, économe en mémoire et sûre. Les types de TArray sont définis par deux propriétés : le type d'élément et un allocateur facultatif.
Le type d'élément est le type des objets qui seront stockés dans la matrice. TArray est un conteneur homogène, ce qui signifie que tous ses éléments sont strictement du même type ; il n'est pas possible de stocker des éléments de types différents dans un même TArray.
L'allocateur est très souvent omis et sera remplacé par défaut par celui qui convient à la plupart des cas d'utilisation. Il définit la disposition des objets en mémoire et la façon dont la matrice doit s'étendre pour accueillir plus d'éléments. Il existe plusieurs allocateurs différents que vous pouvez utiliser si le comportement par défaut ne vous convient pas, ou vous pouvez créer le vôtre. Nous en parlerons plus en détail plus tard.
TArray est un type de valeur, ce qui signifie qu'il doit être traité de la même manière que n'importe quel autre type intégré, comme int32 ou float. Il n'est pas conçu pour être enrichi. En outre, il n'est pas recommandé de créer ou de détruire des instances de TArray avec les commandes new et delete. Les éléments sont également des types de valeur et la matrice les possède. La destruction d'un TArray aura pour résultat la destruction de tous les éléments qu'il contient encore. Créer une variable TArray à partir d'une autre variable copie ses éléments dans la nouvelle variable ; il n'y a pas d'état partagé.
Créer et remplir une matrice
Pour créer une matrice, définissez-la comme suit :
TArray<int32> IntArray;Cela crée une matrice vide conçue pour contenir une séquence d'entiers. Le type d'élément peut être n'importe quel type valeur qui peut être copié et détruit selon les règles normales des types valeur en C++, comme int32, FString, TSharedPtr, etc. Aucun allocateur n'a été spécifié, donc le TArray utilisera l'allocateur par défaut basé sur le tas. À ce stade, aucune mémoire n'a été allouée.
Le TArray (comme beaucoup de conteneurs de l'Unreal Engine) suppose que le type d'élément est facilement déplaçable, ce qui signifie que les éléments peuvent être déplacés en toute sécurité d'un emplacement mémoire à un autre en copiant directement leurs octets bruts.
Les TArrays peuvent être remplis de plusieurs façons. L'une d'elles consiste à utiliser la fonction Init, qui remplit une matrice avec plusieurs copies d'un élément :
IntArray.Init(10, 5);
// IntArray == [10,10,10,10,10]Les fonctions Add et Emplace permettent de créer de nouveaux éléments à la fin de la matrice :
TArray<FString> StrArr;
StrArr.Add (TEXT("Hello"));
StrArr.Emplace(TEXT("World"));
// StrArr == ["Hello","World"]L'allocateur de la matrice fournit de la mémoire au fur et à mesure que de nouveaux éléments y sont ajoutés. L'allocateur par défaut ajoute assez de mémoire pour plusieurs nouveaux éléments dès que de taille de la matrice actuelle est dépassée. Les fonctions Add et Emplace font à peu près la même chose, mais avec une légère différence :
Add(ouPush) copie (ou déplace) une instance du type d'élément dans la matrice.Emplaceutilise les arguments qui lui sont fournis pour construire une nouvelle instance du type d'élément.
Dans le cas de notre TArray<FString>, Add crée un FString temporaire à partir du littéral de chaîne, puis déplace le contenu de ce FString temporaire vers un nouveau FString à l'intérieur du conteneur tandis que Emplace crée juste le nouveau FString directement à l'aide du littéral de chaîne. Le résultat final est le même, mais Emplace évite de créer une variable temporaire, ce qui est souvent indésirable pour les types de valeur non triviaux comme FString.
En général, Emplace est préférable à Add, car elle évite de créer des variables temporaires inutiles sur le site d'appel, qui sont ensuite copiées ou déplacées dans le conteneur. En règle générale, utilisez Add pour les types triviaux et Emplace pour les autres cas. Emplace ne sera jamais moins efficace que Add, mais Add peut permettre une meilleure lecture.
Append ajoute plusieurs éléments à la fois, soit depuis un autre TArray, soit depuis un pointeur vers une matrice C classique ainsi que sa taille :
FString Arr[] = { TEXT("of"), TEXT("Tomorrow") };
StrArr.Append(Arr, ARRAY_COUNT(Arr));
// StrArr == ["Hello","World","of","Tomorrow"]AddUnique n'ajoute un nouvel élément au conteneur que si un élément équivalent n'existe pas déjà. L'équivalence est vérifiée à l'aide de l'opérateur == du type d'élément :
StrArr.AddUnique(TEXT("!"));
// StrArr == ["Hello","World","of","Tomorrow","!"]
StrArr.AddUnique(TEXT("!"));
// StrArr is unchanged as "!" is already an elementLa fonction Insert, tout comme Add, Emplace et Append, ajoute un élément unique ou une copie d'une matrice d'éléments à un index donné :
StrArr.Insert(TEXT("Brave"), 1);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]La fonction SetNum peut définir directement le nombre d'éléments de matrice, de nouveaux éléments étant créés à l'aide du constructeur par défaut du type d'élément si le nouveau nombre est supérieur au nombre actuel :
StrArr.SetNum(8);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!","",""]La fonction SetNum supprime également les éléments si le nouveau nombre est inférieur au nombre actuel. Des informations plus détaillées sur la suppression d'éléments seront fournies ultérieurement :
StrArr.SetNum(6);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]Itération
Il existe plusieurs façons d'itérer sur les éléments de votre matrice, mais la méthode recommandée consiste à utiliser la boucle for étendue de C++ :
FString JoinedStr;
for (auto& Str : StrArr)
{
JoinedStr += Str;
JoinedStr += TEXT(" ");
}
// JoinedStr == "Hello Brave World of Tomorrow ! "L'itération normale basée sur les index est également possible :
for (int32 Index = 0; Index != StrArr.Num(); ++Index)
{
JoinedStr += StrArr[Index];
JoinedStr += TEXT(" ");
}Enfin, les matrices ont leur propre type d'itérateur pour plus de contrôle sur vos itérations. Il existe deux fonctions appelées CreateIterator et CreateConstIterator, qui peuvent être utilisées respectivement pour l'accès en lecture-écriture ou en lecture seule aux éléments :
for (auto It = StrArr.CreateConstIterator(); It; ++It)
{
JoinedStr += *It;
JoinedStr += TEXT(" ");
}Tri
Les matrices peuvent être triées grâce à un simple appel de la fonction Sort :
StrArr.Sort();
// StrArr == ["!","Brave","Hello","of","Tomorrow","World"]Ici, les valeurs sont triées à l'aide de l'opérateur < du type d'élément. Dans le cas de FString, il s'agit d'une comparaison lexicographique insensible à la casse. Il est également possible d'implémenter un prédicat binaire afin de définir d'autres sémantiques d'ordre, comme ceci :
StrArr.Sort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Hello","Brave","World","Tomorrow"]Les chaînes sont désormais triées par longueur. Observez comment les trois chaînes de même longueur (Hello, Brave et World) ont changé d'ordre selon leur position initiale dans la matrice. C'est parce que la fonction Sort est instable et que l'ordre relatif des éléments équivalents (les chaînes sont équivalentes ici, car le prédicat ne compare que la longueur) n'est pas garanti. Sort est implémenté comme un tri rapide.
La fonction HeapSort, avec ou sans prédicat binaire, peut être utilisée pour réaliser un tri sur le tas. Votre décision de l'utiliser dépend de vos données et de l'efficacité de votre tri par rapport à la fonction Sort. À l'instar de Sort, HeapSort n'est pas stable. Si nous avions utilisé HeapSort au lieu de Sort ci-dessus, le résultat obtenu (le même dans le cas présent) serait le suivant :
StrArr.HeapSort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Hello","Brave","World","Tomorrow"]Enfin, StableSort permet de garantir l'ordre relatif des éléments équivalents après le tri. Si nous avions appelé StableSort au lieu de Sort ou HeapSort ci-dessus, le résultat aurait été le suivant :
StrArr.StableSort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Brave","Hello","World","Tomorrow"]Autrement dit, "Brave ", "Hello" et "World" ont dans le même ordre relatif après avoir été triés de façon lexicographique. StableSort est implémenté sous forme de tri par fusion.
Requêtes
Nous pouvons demander à la matrice combien d'éléments elle contient à l'aide de la fonction Num :
int32 Count = StrArr.Num();
// Count == 6Si vous avez besoin d'un accès direct à la mémoire de la matrice, par exemple pour l'interopérabilité avec une API de type C, vous pouvez utiliser la fonction GetData pour renvoyer un pointeur vers les éléments de la matrice. Ce pointeur n'est valide que tant que la matrice existe et qu'aucune opération de modification n'a été effectuée sur celle-ci. Seuls les premiers index Num de StrPtr peuvent être déréférencés :
FString* StrPtr = StrArr.GetData();
// StrPtr[0] == "!"
// StrPtr[1] == "of"
// ...
// StrPtr[5] == "Tomorrow"
// StrPtr[6] - undefined behaviorSi le conteneur est une constante, le pointeur renvoyé sera aussi une constante.
Vous pouvez également demander au conteneur la taille des éléments :
uint32 ElementSize = StrArr.GetTypeSize();
// ElementSize == sizeof(FString)Pour récupérer des éléments, vous pouvez utiliser l'opérateur d'indexation [] et lui transmettre un index partant de zéro à l'élément de votre choix :
FString Elem1 = StrArr[1];
// Elem1 == "of"La transmission d'un index non valide — inférieur à 0 ou supérieur ou égal à Num() — entraînera une runtime error. Vous pouvez demander au conteneur si un index particulier est valide en utilisant la fonction 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 == falseL'opérateur [] renvoie une référence. Il peut donc être utilisé aussi pour muter les éléments à l'intérieur de la matrice, en supposant que votre matrice n'est pas une constante :
StrArr[3] = StrArr[3].ToUpper();
// StrArr == ["!","of","Brave","HELLO","World","Tomorrow"]À l'instar de la fonction GetData, l'opérateur [] renvoie une référence constante si la matrice est constante. Il est également possible d'indexer à rebours depuis la fin de la matrice en utilisant la fonction Last. Par défaut, l'index est défini sur zéro. La fonction Top est un équivalent de Last, mais elle n'accepte pas d'index :
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"Il est possible de demander à la matrice si elle contient un élément spécifique :
bool bHello = StrArr.Contains(TEXT("Hello"));
bool bGoodbye = StrArr.Contains(TEXT("Goodbye"));
// bHello == true
// bGoodbye == falseIl est également possible de demander à la matrice si elle contient un élément qui correspond à un prédicat spécifique :
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 == falseVous pouvez rechercher des éléments à l'aide de la famille de fonctions Find. Pour vérifier si un élément existe et renvoyer son index, on utilise Find :
int32 Index;
if (StrArr.Find(TEXT("Hello"), Index))
{
// Index == 3
}Cela définit Index comme étant l'index du premier élément trouvé. S'il existe des éléments en double et que l'on souhaite trouver l'index du dernier élément, on utilise la fonction FindLast à la place :
int32 IndexLast;
if (StrArr.FindLast(TEXT("Hello"), IndexLast))
{
// IndexLast == 3, because there aren't any duplicates
}Ces deux fonctions renvoient un booléen pour indiquer si un élément a été trouvé ou non, tout en écrivant l'index de cet élément dans une variable lorsqu'il a été trouvé.
Les fonctions Find et FindLast peuvent aussi renvoyer directement un index d'élément. C'est le cas si vous ne transmettez pas l'index comme un argument explicite. Elle peut être plus brève que la fonction ci-dessus et la fonction que vous utiliserez dépendra de ce qui convient le mieux à votre besoin ou à votre style.
Si aucun élément n'a été trouvé, la valeur spéciale Index_NONE est renvoyée :
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 fonctionne de manière similaire, mais permet de comparer des éléments avec un objet arbitraire. Avec les fonctions Find, l'argument est converti en type d'élément (FString dans le cas présent) avant que la recherche ne commence. Avec IndexOfByKey, la clé est comparée directement, ce qui permet d'effectuer des recherches même lorsque le type de clé n'est pas directement convertible en type d'élément.
IndexOfByKey fonctionne pour tous les types de point pour lesquels operator==(ElementType, KeyType) existe. IndexOfByKey renvoie l'index du premier élément trouvé, ou INDEX_NONE si aucun élément n'a été trouvé :
int32 Index = StrArr.IndexOfByKey(TEXT("Hello"));
// Index == 3La fonction IndexOfByPredicate trouve l'index du premier élément qui correspond au prédicat spécifié, et renvoie la valeur spéciale INDEX_NONE si aucun élément n'a été trouvé :
int32 Index = StrArr.IndexOfByPredicate([](const FString& Str){
return Str.Contains(TEXT("r"));
});
// Index == 2Au lieu de renvoyer les index, il est possible de renvoyer des pointeurs vers les éléments trouvés. FindByKey fonctionne comme IndexOfByKey. Il compare les éléments à un objet arbitraire, mais renvoie un pointeur vers l'élément trouvé. Si elle ne trouve pas d'élément, elle renvoie nullptr :
auto* OfPtr = StrArr.FindByKey(TEXT("of")));
auto* ThePtr = StrArr.FindByKey(TEXT("the")));
// OfPtr == &StrArr[1]
// ThePtr == nullptrFindByPredicate peut être utilisée comme IndexOfByPredicate, à l'exception qu'elle renvoie un pointeur au lieu d'un index :
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 == nullptrEnfin, vous pouvez récupérer une matrice d'éléments correspondant à un prédicat particulier avec la fonction FilterByPredicate :
auto Filter = StrArray.FilterByPredicate([](const FString& Str){
return !Str.IsEmpty() && Str[0] < TEXT('M');
});Suppression
Vous pouvez supprimer des éléments de la matrice à l'aide de la famille de fonctions Remove. La fonction Remove supprime tous les éléments considérés comme égaux à l'élément que vous fournissez, selon la fonction operator== du type d'élément. Par exemple :
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]Vous pouvez également utiliser RemoveSingle pour supprimer le premier élément correspondant de la matrice. Cette option est utile si vous savez que votre matrice peut contenir des doublons et que vous souhaitez supprimer un seul élément, ou si vous savez que votre matrice ne peut contenir qu'un seul élément correspondant :
ValArr.RemoveSingle(30);
// ValArr == [10,5,10,15,25,30]Il est également possible de supprimer des éléments par leur index basé sur zéro en utilisant la fonction RemoveAt. Vous pouvez utiliser IsValidIndex pour vérifier que la matrice comporte un élément avec l'index que vous voulez fournir, car transmettre un index non valide à cette fonction entraînera une runtime error.
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 99La fonction RemoveAll permet également de supprimer les éléments correspondant à un prédicat. Par exemple, supprimer toutes les valeurs qui sont des multiples de 3 :
ValArr.RemoveAll([](int32 Val) {
return Val % 3 == 0;
});
// ValArr == [10,5,25]Dans tous ces cas, lorsque des éléments sont supprimés, les éléments suivants sont décalés vers des index inférieurs, car il ne peut jamais y avoir de trous dans la matrice.
Le processus de décalage entraîne une surcharge. Si l'ordre des éléments restants importe peu, cette surcharge peut être réduite grâce à des fonctions RemoveSwap, RemoveAtSwap et RemoveAllSwap. Elles fonctionnent comme les variantes classiques, mais elles ne garantissent pas l'ordre des éléments restants, ce qui leur permet d'effectuer ces tâches plus rapidement :
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]
Enfin, la fonction Empty permet de tout supprimer dans la matrice :
ValArr2.Empty();
// ValArr2 == []Opérateurs
Les matrices sont des types de valeur standard et, à ce titre, peuvent être copiées par le constructeur de copie standard ou l'opérateur d'assignation. Étant donné que les matrices sont propriétaires de leurs éléments, la copie d'une matrice est complexe. La nouvelle matrice aura donc sa propre copie des éléments :
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];Comme alternative à la fonction Append, vous pouvez concaténer des matrices avec l'opérateur += :
ValArr4 += ValArr3;
// ValArr4 == [5,2,3,1,2,3]TArray prend également en charge la sémantique de déplacement, qui peut être appelée à l'aide de la fonction MoveTemp. Après un déplacement, la matrice source reste forcément vide :
ValArr3 = MoveTemp(ValArr4);
// ValArr3 == [5,2,3,1,2,3]
// ValArr4 == []Les matrices peuvent être comparées à l'aide des opérateurs == et !=. L'ordre des éléments est important : deux matrices ne sont identiques que si elles contiennent le même nombre d'éléments dans le même ordre. Les éléments sont comparés à l'aide de leur propre opérateur == :
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
Heap
Les fonctions de TArray prennent en charge une structure de type tas binaire. Un tas est un type d'arborescence binaire dans lequel chaque nœud parent est de niveau équivalent ou supérieur à tous ses nœuds enfants. Lorsqu'il est implémenté comme une matrice, le nœud racine de l'arborescence est à l'élément 0 et les index des nœuds enfants gauche et droit d'un nœud à l'index N sont 2N+1 et 2N+2 respectivement. Les enfants ne sont pas dans un ordre particulier les uns par rapport aux autres.
N'importe quelle matrice peut être convertie en tas en appelant la fonction Heapify. Cette surcharge permet d'accepter ou non un prédicat. La version sans prédicat utilisera alors l'opérateur du type d'élément < pour déterminer l'ordre :
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]Voici une visualisation de l'arborescence :
Les nœuds de l'arborescence peuvent être lus de gauche à droite et de haut en bas dans l'ordre des éléments de la matrice en tas. Notez que la matrice n'est pas nécessairement triée après sa transformation en tas. Bien qu'une matrice triée puisse également constituer un tas valide, la définition d'un tas est suffisamment souple pour autoriser plusieurs tas valides pour un même ensemble d'éléments.
De nouveaux éléments peuvent être ajoutés au tas via la fonction HeapPush, qui réordonne les autres nœuds pour maintenir le tas :
HeapArr.HeapPush(4);
// HeapArr == [1,2,4,3,4,5,8,10,7,9,6]Les fonctions HeapPop et HeapPopDiscard sont utilisées pour supprimer le nœud supérieur du tas. La différence entre les deux est que la première prend une référence à un type d'élément pour renvoyer une copie de l'élément supérieur, et que la seconde supprime simplement le nœud supérieur sans aucun renvoi. Les deux fonctions entraînent le même résultat dans la matrice, et le tas est de nouveau maintenu en réorganisant les autres éléments en conséquence :
int32 TopNode;
HeapArr.HeapPop(TopNode);
// TopNode == 1
// HeapArr == [2,3,4,6,4,5,8,10,7,9]HeapRemoveAt supprime un élément de la matrice à un index donné, puis réordonne les éléments pour maintenir le tas :
HeapArr.HeapRemoveAt(1);
// HeapArr == [2,4,4,6,9,5,8,10,7]Les fonctions HeapPush, HeapPop, HeapPopDiscard et HeapRemoveAt ne doivent être appelées que lorsque la structure est déjà un tas valide, par exemple après un appel de Heapify ou toute autre opération sur le tas, ou en manipulant manuellement la matrice pour en faire un tas.
Chacune de ces fonctions, y compris Heapify, peut prendre un prédicat binaire facultatif pour déterminer l'ordre des éléments de nœud dans le tas. Par défaut, les opérations sur le tas utilisent l'opérateur < du type d'élément pour déterminer l'ordre. Lorsque vous utilisez un prédicat personnalisé, il est important d'utiliser le même prédicat pour toutes les opérations sur le tas.
Enfin, le nœud supérieur du tas peut être inspecté à l'aide de HeapTop, sans modifier la matrice :
int32 Top = HeapArr.HeapTop();
// Top == 2Marge
Étant donné que les matrices peuvent être redimensionnées, elles utilisent une quantité variable de mémoire. Pour éviter les réallocations à chaque ajout d'éléments, les allocateurs fournissent généralement plus de mémoire que celle demandée afin que les futurs appels de Add ne nuisent pas aux performances. De la même manière, la suppression d'éléments ne libère généralement pas de la mémoire. Cela laisse à la matrice des éléments en marge, c'est-à-dire des emplacements de stockage préalloués pour des éléments qui ne sont pas actuellement utilisés. La marge dans une matrice est définie comme la différence entre le nombre d'éléments stockés dans la matrice et le nombre d'éléments que la matrice pourrait contenir avec la mémoire qui lui a été allouée.
Étant donné qu'une matrice construite par défaut n'alloue pas de mémoire, la marge sera initialement nulle. Pour déterminer la marge d'une matrice, utilisez la fonction GetSlack. Le nombre maximal d'éléments que peut contenir la matrice avant que le conteneur ne soit réalloué peut être obtenu à l'aide de la fonction Max. GetSlack est équivalent à la différence entre Max et 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 quantité de marge dans un conteneur après la réattribution est décidée par l'allocateur. Les utilisateurs ne doivent donc pas s'attendre à ce que la marge reste constante.
Bien que la gestion de la marge ne soit pas nécessaire, vous pouvez l'utiliser à votre avantage pour fournir des indications d'optimisation à la matrice. Par exemple, si vous savez que vous allez ajouter 100 nouveaux éléments à la matrice, vous pouvez vous assurer d'avoir au moins 100 emplacements en marge avant l'ajout, afin que la matrice n'ait pas besoin d'allouer de mémoire pendant l'insertion des nouveaux éléments. La fonction Empty, mentionnée ci-dessus, accepte un argument facultatif pour la marge :
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);
Il existe une fonction Reset qui fonctionne de la même manière que la fonction Empty, sauf qu'elle ne libère pas la mémoire si la marge demandée est déjà disponible avec l'allocation actuelle. Cependant, elle alloue plus de mémoire si la marge demandée est plus importante :
SlackArray.Reset(0);
// SlackArray.GetSlack() == 3
// SlackArray.Num() == 0
// SlackArray.Max() == 3
SlackArray.Reset(10);
// SlackArray.GetSlack() == 10
// SlackArray.Num() == 0
// SlackArray.Max() == 10Enfin, vous pouvez supprimer toute marge à l'aide de la fonction Shrink, qui ramène l'allocation à la taille minimale requise pour contenir les éléments actuels. L'option Shrink n'a aucun effet sur les éléments de la matrice :
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
Mémoire brute
TArray n'est qu'un wrapper autour de la mémoire allouée. Il peut être utile de le considérer ainsi en modifiant directement les octets de l'allocation et en créant vous-même les éléments. Le TArray essaiera toujours d'optimiser les opérations avec les informations dont il dispose, mais vous devrez parfois passer à un niveau inférieur.
Les fonctions suivantes vous donnent un accès rapide et de bas niveau au TArray et aux données qu'il contient. Cependant, en cas d'usage inapproprié, elles peuvent placer le conteneur dans un état invalide et provoquer un comportement indéfini. C'est à vous de renvoyer le conteneur à un état valide après avoir appelé ces fonctions, mais avant qu'une autre fonction normale ne soit appelée.
Les fonctions AddUninitialized et InsertUninitialized permettent d'ajouter de l'espace non initialisé à la matrice. Elles fonctionnent respectivement comme les fonctions Add et Insert, mais sans appeler le constructeur du type d'élément. Cela peut être utile pour éviter l'appel aux constructeurs. Par exemple dans des cas comme celui-ci, où vous prévoyez d'écraser entièrement la structure avec un appel à 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]Vous pouvez également utiliser cette fonctionnalité pour réserver de la mémoire pour les objets que vous envisagez de créer vous-même :
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 et InsertZeroed fonctionnent de façon similaire, sauf qu'elles mettent aussi à zéro les octets de l'espace ajouté/inséré :
struct S
{
S(int32 InInt, void* InPtr, float InFlt)
: Int(InInt)
, Ptr(InPtr)
, Flt(InFlt)
{
}
int32 Int;
void* Ptr;
Les fonctions SetNumUninitialized et SetNumZeroed ont un fonctionnement similaire à la fonction SetNum, mais dans le cas où la nouvelle valeur est supérieure à la valeur actuelle, l'espace pour les nouveaux éléments n'est pas initialisé ou mis à zéro bit à bit. Comme dans le cas des fonctions AddUninitialized et InsertUninitialized, vous devez vous assurer que les nouveaux éléments sont correctement construits dans le nouvel espace, le cas échéant :
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);
Utilisez les familles de fonctions "Uninitialized" et "Zeroed" avec précaution. Si un type d'élément comprend un membre nécessitant une construction, ou qui n'a pas d'état valide lorsqu'il est mis à zéro bit à bit, cela peut entraîner des éléments de matrice non valides et un comportement indéfini. Ces fonctions sont particulièrement utiles sur les matrices de types qui ne changeront probablement jamais, comme FMatrix ou FVector.
Divers
La fonction BulkSerialize est une fonction de sérialisation qui peut être utilisée comme une alternative à l'opérateur << afin de sérialiser la matrice sous forme de bloc d'octets bruts, plutôt que de sérialiser élément par élément. Cela peut améliorer les performances pour des éléments simples, comme un type intégré ou une structure de données basique.
Les fonctions CountBytes et GetAllocatedSize permettent d'estimer la quantité de mémoire utilisée par la matrice. CountBytes prend un FArchive en paramètre, alors que GetAllocatedSize peut être appelé directement. Ces fonctions sont généralement utilisées pour générer des rapports statistiques.
Les fonctions Swap et SwapMemory prennent toutes deux deux index et échangent la valeur des éléments au niveau de ces index. Elles sont équivalentes, à la différence que Swap effectue des vérifications supplémentaires sur les index et déclenche une assertion si ceux-ci sont hors limites.