Unreal Engine で最もシンプルなコンテナ クラスは TArray です。 TArray は、同じタイプの他のオブジェクトのシーケンス (「要素」と呼ばれる) の所有権と編成を実行します。 TArray はシーケンスであるため、その要素には明確な順序があり、その機能は決定論的にオブジェクトとその順序を操作するために使用されます。
TArray
TArray は、Unreal Engine 内で最もよく使用されるコンテナ クラスです。 高速で、メモリ効率が高く、安全です。 TArray 型は、Element type とオプションのアロケーターの 2 つのプロパティで定義されます。
要素の型は、配列に格納されるオブジェクトの型です。 TArray は同種のコンテナと呼ばれるものであり、その要素のすべてが厳密に同じ型であることを意味します。単一の TArray 内に異なる型の要素を格納することはできません。
多くの場合、アロケーターは省略され、デフォルトではほとんどのユースケースに適したものになります。 これは、メモリ内でのオブジェクトのレイアウト方法と、より多くの要素を許可するために配列をどのように拡張するかを定義します。 デフォルト動作が適切ではない場合や、独自に作成する場合は、使用できるアロケーターがいくつかあります。 詳細については後ほど説明します。
TArray は値の型です。つまり、int32 や float などの他の組み込み型と同じように扱われます。 これは拡張されるよう設計されていないため、new および delete 関数を使用した TArray インスタンスの作成または破棄はお勧めしません。 要素も値型であり 配列が所有します。 TArray を破棄すると、結果として、まだ格納されている要素も破棄されます。 別の TArray 変数を作成すると、その要素が新しい変数にコピーされます。共有状態はありません。
配列を作成してデータを格納する
配列を作成するには、次のように定義します。
TArray<int32> IntArray;整数のシーケンスを保持するための空の配列が作成されます。 要素型は、int32、FString、TSharedPtr などの通常の C++ 値ルールに従ってコピー可能で破壊可能な任意の値型です。 アロケーターが指定されていないため、TArray はデフォルトのヒープベースのアロケーターを使用します。 この時点でメモリは割り当てられていません。
TArray (多くの Unreal Engine コンテナと同様) では、要素の型は自明に再配置可能であると想定されています。つまり、未加工のバイトを直接コピーすることで、メモリ内のある場所から別の場所に要素を安全に移動できます。
TArray は、複数の方法で値を設定することができます。 1 つの方法は Init 関数を使用するもので、要素のコピーを配列に入力することで、
IntArray.Init(10, 5);
// IntArray == [10,10,10,10,10]Add 関数および Emplace 関数は、次のように配列の最後に新しい要素を作成することができます。
TArray<FString> StrArr;
StrArr.Add (TEXT("Hello"));
StrArr.Emplace(TEXT("World"));
// StrArr == ["Hello","World"]配列のアロケーターは、新しい要素が配列に追加されるときに必要に応じてメモリを提供します。 デフォルトのアロケーターは、現在の配列サイズを超えるたびに複数の新しい要素のための十分なメモリを追加します。 Add と Eplace はほとんど同じ処理を行いますが、若干違いがあります。
Add(またはPush) は、要素型のインスタンスを配列にコピー (または移動) させます。Emplaceは、指定された引数を使用して、要素型の新しいインスタンスを構築します。
TArray<FString> の場合、Add は文字列リテラルから一時的な FString を作成し、その一時的な FString の内容をコンテナ内の新しい FString に移動しますが、Emplace は文字列リテラルを使用して新しい FString を直接作成します. 最終結果は同じですが、Emplace は一時的な変数の作成を回避します。これは、FString などの重要な値の型では望ましくないことがよくあります。
一般的には、コンテナにコピーまたは移動される呼び出しサイトで不要な一時変数が作成されないようにするには、Add よりも Emplace をお勧めします。 経験則として、トリビアル型には Add を使用し、それ以外の場合は Emplace を使用します。 Emplace が Add よりも効率が低いということはありませんが、Add の方が読みやすくなります。
Append では、別の TArray または、通常の C 配列へのポインタから一度に複数の要素を追加します。
FString Arr[] = { TEXT("of"), TEXT("Tomorrow") };
StrArr.Append(Arr, ARRAY_COUNT(Arr));
// StrArr == ["Hello","World","of","Tomorrow"]AddUnique は、同等の要素がすでに存在しない場合にのみ、コンテナに新しい要素を追加します。 同等であるかどうかは、要素型の演算子 == を使用してチェックされます。
StrArr.AddUnique(TEXT("!"));
// StrArr == ["Hello","World","of","Tomorrow","!"]
StrArr.AddUnique(TEXT("!"));
// StrArr is unchanged as "!" is already an elementAdd や Emplace や Append と同様に Insert ノードは、指定されたインデックスで単一の要素または要素の配列のコピーを追加します。
StrArr.Insert(TEXT("Brave"), 1);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]SetNum 機能は配列要素の数を直接設定できます。新しい数が現在の数より大きい場合は、要素型のデフォルトのコンストラクタを使って新しい要素が作成されます。
StrArr.SetNum(8);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!","",""]SetNum は、新しい数値が現在の数値より小さい場合にも要素を削除します。 要素の削除に関する詳細:
StrArr.SetNum(6);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]イテレーション
配列の要素に対してイテレートする方法はいくつかありますが、C++ の ranged-for 関数を使用することをお勧めします。
FString JoinedStr;
for (auto& Str : StrArr)
{
JoinedStr += Str;
JoinedStr += TEXT(" ");
}
// JoinedStr == "Hello Brave World of Tomorrow ! "通常のインデックス ベースのイテレーションも可能です。
for (int32 Index = 0; Index != StrArr.Num(); ++Index)
{
JoinedStr += StrArr[Index];
JoinedStr += TEXT(" ");
}最後に、配列にはイテレーションをより細かく制御するための独自のイテレーター型もあります。 CreateIterator と CreateConstIterator という 2 つの関数があり、それぞれ要素への読み取り/書き込みまたは読み取り専用のアクセスに使用することができます。
for (auto It = StrArr.CreateConstIterator(); It; ++It)
{
JoinedStr += *It;
JoinedStr += TEXT(" ");
}並び替え
配列は、Sort 関数を呼び出すことで簡単にソートすることができます。
StrArr.Sort();
// StrArr == ["!","Brave","Hello","of","Tomorrow","World"]ここで、値は要素型の演算子 < によってソートされます FString の場合、これは大文字と小文字を区別しない辞書学的比較です。 バイナリ述語を実装し、次のように順序の異なるセマンティクスを提供することもできます。
StrArr.Sort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Hello","Brave","World","Tomorrow"]これで 各文字列が長さでソートされます。 同じ長さの 3 つの文字列 (「Hello」、「Brave」、「World」) は、配列内でのそれぞれの位置に対して、どのように順番が変更されているのかに注目してください。 これは Sort が不安定で、同等の要素の相対順序が保証されていないためです (述語は長さのみを比較しているため、これらの文字列はここでは同等です)。 Sort はクイックソートとして実装されます。
HeapSort 関数は、バイナリ述語の有無にかかわらず、ヒープ ソートの実行に使用できます。 このノードを使用するかどうかは、使用している特定のデータと、Sort 関数と比較して効率的にソートできるかどうかによります。 Sort と同様に、HeapSort は安定していません。 上記の Sort の代わりに HeapSort を使用した場合、次の結果になります (この場合も同じ)。
StrArr.HeapSort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Hello","Brave","World","Tomorrow"]最後に、StableSort を使用すると、ソート後の要素の相対的な順序を保証できます。 上記の Sort や HeapSort の代わりに StableSort を呼び出した場合、結果は次のようになります。
StrArr.StableSort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Brave","Hello","World","Tomorrow"]つまり、「Brave」、「Hello」、「World」は、以前に辞書学的にソートされた後、同じ相対的な順序のままになっています。 StableSort はマージ ソートとして実装されます。
クエリ
Num 関数を使用して、保持している要素の数を配列に問い合わせることができます。
int32 Count = StrArr.Num();
// Count == 6C スタイル API との相互運用性のために、配列メモリに直接アクセスする必要がある場合、GetData 関数を使用して、配列の要素へのポインタを返すことができます。 このポインタは、配列が存在するかぎり、その配列に対して変更演算が行われるまで有効です。 StrPtr から最初の Num インデックスのみが逆参照可能です。
FString* StrPtr = StrArr.GetData();
// StrPtr[0] == "!"
// StrPtr[1] == "of"
// ...
// StrPtr[5] == "Tomorrow"
// StrPtr[6] - undefined behaviorコンテナが定数の場合、返されるポインタも定数になります。
また、コンテナに要素の大きさを問い合わせることもできます。
uint32 ElementSize = StrArr.GetTypeSize();
// ElementSize == sizeof(FString)要素を取得するには、インデックス演算子 [] を使用して、ゼロベースのインデックスを目的の要素に渡すことができます。
FString Elem1 = StrArr[1];
// Elem1 == "of"無効なインデックス (0 未満または Num() 以上) を渡すと、ランタイム エラーが発生します。 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 == false演算子 [] は参照を返すため、配列内の要素を変更するために使用することもできます (配列が定数でないことを前提とします)。
StrArr[3] = StrArr[3].ToUpper();
// StrArr == ["!","of","Brave","HELLO","World","Tomorrow"]GetData 関数と同様に、配列が定数の場合、演算子 [] は定数の参照を返します。 Last 関数を使用して、配列の末尾から逆向きにインデックスを作成すすることもできます。 インデックスのデフォルトは「0」です。 Top 関数は、インデックスを使用しない点を除き、Last と同義です。
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"配列に対して特定の要素が含まれているかどうかを確認できます。
bool bHello = StrArr.Contains(TEXT("Hello"));
bool bGoodbye = StrArr.Contains(TEXT("Goodbye"));
// bHello == true
// bGoodbye == falseまたは、特定の述語に一致する要素が含まれているかどうかを配列に対して確認できます。
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 == false関数の Find ファミリーを使用して要素を見つけることができます。 要素が存在するかどうかをチェックし、そのインデックスを返すには、Find を使用します。
int32 Index;
if (StrArr.Find(TEXT("Hello"), Index))
{
// Index == 3
}これにより、インデックスが最初に見つかった要素のインデックスに設定されます。 重複する要素があり、最後の要素のインデックスを見つける場合は、代わりに FindLast 関数を使用します。
int32 IndexLast;
if (StrArr.FindLast(TEXT("Hello"), IndexLast))
{
// IndexLast == 3, because there aren't any duplicates
}これらの機能はどちらも、要素が見つかったかどうかを示す bool を返し、見つかった場合はその要素のインデックスを変数に書き込みます。
Find と FindLast は要素インデックスを直接返すこともできます。 明示的な引数としてインデックスを渡さない場合、これらは全て独自の引数を生成します。 これは上記の関数よりもシンプルで、具体的なニーズやスタイルに適した関数は異なりますが、使用する関数は異なります。
要素が見つからなかった場合は、特別な 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 も同様に機能しますが、要素と任意のオブジェクトを比較できます。 Find 関数では、検索が開始される前に引数が要素の型 (この場合は FString) に変換されます。 IndexOfByKey を使用するとキーが直接比較され、キーの型が要素の型に直接変換できない場合でも検索がサポートされます。
IndexOfByKey では、演算子 ==(ElementType, KeyType) が存在するあらゆるキー タイプに対して実行されます。 IndexOfByKey は見つかった最初の要素のインデックスを返します。要素が見つからなかった場合は INDEX_NONE を返します。
int32 Index = StrArr.IndexOfByKey(TEXT("Hello"));
// Index == 3IndexOfByPredicate 関数は、指定された述語に一致する最初の要素のインデックスを検索し、見つからなかった場合は特別な INDEX_NONE 値を返します。
int32 Index = StrArr.IndexOfByPredicate([](const FString& Str){
return Str.Contains(TEXT("r"));
});
// Index == 2インデックスを返す代わりに、見つかった要素へのポインタを返せます。 FindByKey は IndexOfByKey と同様に機能し、要素を任意のオブジェクトと比較しますが、検出された要素へのポインタを返します。 要素が見つからない場合は、nullptr を返します。
auto* OfPtr = StrArr.FindByKey(TEXT("of")));
auto* ThePtr = StrArr.FindByKey(TEXT("the")));
// OfPtr == &StrArr[1]
// ThePtr == nullptrFindByPredicate は IndexOfByPredicate と同様に使用できますが、インデックスの代わりにポインタを返す点が異なります。
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 == nullptr最後に、FilterByPredicate 関数を使用すると、特定の述語に一致する要素の配列を取得できます。
auto Filter = StrArray.FilterByPredicate([](const FString& Str){
return !Str.IsEmpty() && Str[0] < TEXT('M');
});除去
関数の Remove ファミリーを使用すると、配列から要素を消去できます。 Remove 関数は、要素型の演算子 == 関数に従って、指定した要素と等しいと見なされるすべての要素を削除します。 たとえば、次のようになります。
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]RemoveSingle を使用して配列内の最初の一致要素を削除することもできます。 これは、配列に重複が含まれる可能性があることがわかっている場合に便利で、消去したいのは 1 つだけである場合、または配列に一致する要素が 1 つだけ含まれることがわかっている場合の最適化として有用です。
ValArr.RemoveSingle(30);
// ValArr == [10,5,10,15,25,30]RemoveAt 関数を使用して、ゼロベースのインデックスで要素を削除することもできます。 無効なインデックスをこの関数に渡すとランタイム エラーが発生するため、IsValidIndex を使用して、指定する予定のインデックスを持つ要素が配列にあることを検証することをお勧めします。
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 99RemoveAll 関数を使用して述語に一致する要素を削除することもできます。 たとえば、3 の倍数であるすべての値を削除します。
ValArr.RemoveAll([](int32 Val) {
return Val % 3 == 0;
});
// ValArr == [10,5,25]これらのケースでは全て、要素が削除されると、後続の要素はより低いインデックスにシャッフルされます。これは、配列に穴が残らないようにするためです。
このシャッフル処理にはオーバーヘッドがかかります。 残りの要素の順序が気にならない場合、RemoveSwap 関数、RemoveAtSwap 関数、および RemoveAllSwap 関数を使用することで、このオーバーヘッドを減らすことができます。これは、残りの要素の順序を保証しないことを除けば、入れ替わらないバージョンと同じように機能し、より迅速にタスクを完了することができます。
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]
最後に、Empty 関数は配列から全てを削除します。
ValArr2.Empty();
// ValArr2 == []演算子
配列は、通常の値型であるため、標準コピー コンストラクタまたは代入演算子でコピーできます。 配列はそれぞれの要素を所有するため、配列の厳密なコピーは深く行われないため、新しい配列には要素の独自のコピーが存在します。
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];Append 関数の代替手段として、演算子 += を使用して配列を結合できます。
ValArr4 += ValArr3;
// ValArr4 == [5,2,3,1,2,3]TArray は、MoveTemp 関数を使用して呼び出すことができるムーブ セマンティクスもサポートしています。 移動後、元の配列は確実に空の状態になります。
ValArr3 = MoveTemp(ValArr4);
// ValArr3 == [5,2,3,1,2,3]
// ValArr4 == []配列は演算子 == と演算子 != を使用して比較できます。 要素の順序は重要です。2 つの配列は、同じ順序で同じ数の要素がある場合にのみ等しいものとなります。 要素は独自の演算子 == を使用して比較されます。
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 (ヒープ)
TArray には、バイナリ ヒープ データ構造をサポートする関数があります。 ヒープは、親ノードがすべての子ノードと同等か、その子ノードの前に順序付けされる、二分木の型です。 配列として実装する場合、ツリーのルート ノードは要素 0 にあり、インデックス N のノードの左と右の子ノードのインデックスはそれぞれ 2N+1 と 2N+2 です。 これらの子は、互いに対して特定の順序にはなっていません。
Heapify 関数を呼び出すと、既存の配列をヒープに変えることができます。 これは、述語を取るためにオーバーロードされ、非述語バージョンでは要素型の演算子 < を使用して順序を決定します。
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]以下はこのツリーの図です。
ツリーのノードは、ヒープされた配列の要素の順序で左から右、上から下に読み取られます。 この配列は、ヒープに変換された後に必ずしもソートされるわけではないことに注意してください。 ソートされた配列も有効なヒープですが、ヒープ構造の定義は、同じ要素のセットに対して複数の有効なヒープを許可するほど緩やかです。
新しい要素は HeapPush 関数を使用してヒープに追加することができ、ヒープを維持するために他のノードを並べ替えます。
HeapArr.HeapPush(4);
// HeapArr == [1,2,4,3,4,5,8,10,7,9,6]HeapPop 関数と HeapPopDiscard 関数は、ヒープから最上位のノードを削除するために使用されます。 この 2 つの違いは、前者は要素型への参照を取得し、最上位の要素のコピー返す一方で、後者は上位のノードを何も返すことなく単に削除します。 どちらの関数も結果として配列に同じ変更を行い、他の要素を適切に並べ替えることでヒープが維持されます。
int32 TopNode;
HeapArr.HeapPop(TopNode);
// TopNode == 1
// HeapArr == [2,3,4,6,4,5,8,10,7,9]HeapRemoveAt は、指定されたインデックスにある配列から要素を削除し、ヒープが維持されるように要素を並べ替えます。
HeapArr.HeapRemoveAt(1);
// HeapArr == [2,4,4,6,9,5,8,10,7]HeapPush、HeapPop、HeapPopDiscard および HeapRemoveAt は、その構造体がすでに有効なヒープである場合にのみ呼び出す必要があります。たとえば、Heapify 呼び出し、他のヒープ処理、または配列をヒープに手動で操作した後などです。
Heapify を含むこれらの各関数は、オプションのバイナリ述語を使用して、ヒープ内のノード要素の順序を決定できます。 デフォルトでは、ヒープ演算では要素の型の演算子 < を使用して順序を決定します。 カスタム述語を使用する場合、重要なのはすべてのヒープ演算で同じ述語を使用することです。
最後に、ヒープのトップ ノードは、配列を変更せずに HeapTop を使用して調べることができます。
int32 Top = HeapArr.HeapTop();
// Top == 2スラック
配列はサイズ変更できるため、使用するメモリの量は変数です。 要素が追加されるたびに再割り当てを回避するために、アロケーターは通常、将来の Add 呼び出しで再割り当てによるパフォーマンス ペナルティが発生しないように、リクエストされたよりも多くのメモリを提供します。 同様に、要素を削除しても、通常はメモリが解放されません。 その結果、配列にはスラック要素が残ります。スラック要素は、実質的には、現在使用されていない、効果的に事前に割り当てられた要素ストレージ スロットとなります。 配列内のスラック量は、配列に保存された要素数と、割り当てられているメモリ量で配列が保存できる要素数の差として定義されます。
デフォルトで構成された配列はメモリを割り当てないため、スラックは最初にゼロになります。 GetSlack 関数を使用すると、配列にどれだけのスラックがあるのかを調べることができます。 コンテナが再割り当てされる前に配列が保持できる要素の最大数は、Max 関数で取得できます。 GetSlack は、Max と 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
再割り当て後のコンテナのスラック量はアロケーターによって決定されるため、ユーザーはスラックが定数であることに依存するべきではありません。
スラック管理は必須ではないものの、これを有効に活用すると配列の最適化においてヒントとなります。 たとえば、配列に 100 の新しい要素を追加する予定であれば、追加の前に少なくとも 100 のスラックを確保することで、新しい要素の追加中に配列はメモリを割り当てる必要がなくなります。 前述の Empty 関数は、任意のスラック引数を取ります。
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);
Reset 関数は Empty と同じように動作しますが、リクエストされたスラックが現在の割り当てによってすでに提供されている場合はメモリを解放しません。 ただし、リクエストされたスラックが大きい場合は、より多くのメモリを割り当てます。
SlackArray.Reset(0);
// SlackArray.GetSlack() == 3
// SlackArray.Num() == 0
// SlackArray.Max() == 3
SlackArray.Reset(10);
// SlackArray.GetSlack() == 10
// SlackArray.Num() == 0
// SlackArray.Max() == 10最後に、Shrink 関数を使ってすべてのスラックを除去できます。この関数は、割り当てのサイズを、現在の要素を保持するのに必要な最小サイズに変更します。 Shrink は配列内の要素に影響を与えません。
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
Raw メモリ
TArray は最終的には割り当てられたメモリをラップするラッパーです。 割り当てのバイトを直接変更し、自分で要素を作成することで、そのように扱うことが便利な場合があります。 TArray はすでにある情報で最善の処理を実行しようとしますが、より低いレベルに下げる必要がある場合があります。
以下の関数を使用すると、TArray と TArray が保持するデータへの高速かつ低レベルのアクセスが可能になりますが、誤って使用すると、コンテナを無効な状態に陥らせ、未定義の挙動を引き起こす可能性があります。 これらの関数を呼び出した後、他の通常の関数が呼び出される前に、コンテナを有効な状態に戻すのはご自身の責任です。
AddUninitialized および InsertUninitialized 関数は、未初期化のスペースを配列に追加します。 これらはそれぞれ Add 関数や Insert 関数と同じように機能しますが、要素型のコンストラクタを呼び出すことはありません。 これは、コンストラクタの呼び出しを避けるのに役立ちます。 次の例のような場合、構造体全体を 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]また、この機能を利用して、自分で構築する予定のオブジェクトのためにメモリを確保することもできます。
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 と InsertZeroed は同じように機能しますが、追加/挿入された空間のバイト数もゼロになります。
struct S
{
S(int32 InInt, void* InPtr, float InFlt)
: Int(InInt)
, Ptr(InPtr)
, Flt(InFlt)
{
}
int32 Int;
void* Ptr;
SetNumUninitialized 関数および SetNumZeroed 関数もあり、これらは SetNum と同様に機能します。ただし、新しい数値が現在の数値よりも大きい場合、新しい要素のための領域は初期化されないままか、ビット単位でゼロにされる点が異なります。 AddUninitialized および InsertUninitialized 関数と同様に、次のようにする必要がある場合は、必要に応じて新しい要素が新しい空間に適切に構築されるようにする必要があります。
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);
「Uninitialized」および「Zeroed」ファミリーの関数は慎重に使用してください。 要素型にコンストラクタが必要なメンバー、またはビット単位でゼロ化された有効な状態を持たないメンバーが含まれる場合、無効な配列要素や未定義の挙動を引き起こす可能性があります。 これらの関数は、FMatrix や FVector など、決して変わることのない型の配列に対して最も役立ちます。
その他
BulkSerialize 関数は、要素ごとにシリアル化するのではなく、未加工のバイトのブロックとして配列をシリアル化するための代替演算子 << として使用できるシリアル化関数です。 これにより、組み込み型やプレーン データ構造体などのトリビアル型要素を使用するパフォーマンスが向上する可能性があります。
CountBytes 関数および GetAllocatedSize 関数は、配列が現在使用しているメモリ量を推計するために使用されます。 CountBytes は FArchive を取りますが、GetAllocatedSize は直接呼び出すことができます。 これらの関数は通常、統計情報のレポートに使用されます。
Swap 関数と SwapMemory 関数は、どちらも 2 つのインデックスを受け取り、それらのインデックスの要素の値を入れ替えます。 これらは Swap がインデックスに対して追加のエラー チェックを行い、インデックスが範囲外である場合はアサートすること以外は同じです。