Unreal Engine の主要なコンテナ クラスは TArray
です。 TArray
は同じ型を持つ他のオブジェクト (要素と呼びます) のシーケンスのオーナーシップと構成を定義するクラスです。TArray
はシーケンスで、要素の順序は明確に定義され、その関数を使ってこれらのオブジェクトと順序を確定的に操作します。
TArray
TArray
は、Unreal Engine で最も一般的なコンテナ クラスです。高速で、メモリ効率がよく、安全に作成されています。TArray
型は 2 つのプロパティで定義されます。要素型、そしてオプションのアロケータです。
要素型は、配列に格納されるオブジェクトの型です。TArray
は、いわゆる均一なコンテナです。つまり、すべてのエレメントが完全に同じ型です。一つの TArray
の中に異なる要素型を混在させることはできません。
アロケータは多くの場合省略され、ほとんどのユースケースに適切なアロケータがデフォルト設定になります。メモリでのオブジェクトの置き方や、さらに多くの要素を収容できるように、配列の拡大方法を定義します。デフォルトと違うビヘイビアを使いたい、もしくは自分で書き出すことができると感じた場合は、他にも様々なアロケータを使用できます。これについては、後述します。
TArray
は value 型です。つまり、int32
や float
などの他のビルドイン型と同様に処理されます。継承はされないので、new
/ delete
を使ってヒープ上で TArray
を作成 / 破棄することは通常はありません。要素も value 型で、コンテナに所有されます。TArray
を破棄すると、それがまだ含まれるすべての要素が破棄されます。他のものから TArray 変数を作成すると、その要素は新しい変数へコピーされます。ステートが共有されることはありません。
配列の作成と追加
次のように定義して、配列を作成します。
TArray<int32> IntArray;
整数のシーケンスを持つように設定された空の配列が作成されます。要素型には、int32
、FString
、TSharedPtr
などの一般的な C++ 値ルールに従ってコピーと破棄が可能であれば、どんな値でも使うことができます。アロケータは指定されていないので、TArray
はデフォルトのヒープを基本とする割り当てになります。この時点では、メモリは割り当てられていません。
TArrays
を埋める方法は何通りかあります。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
と Emplace
はほぼ同じですが、以下の点が異なります。
Add
(またはPush
) は要素型のインスタンスを配列へコピー (または移動) します。Emplace
は与えた引数を使って要素型の新しいインスタンスを構築します。
TArray<FString>
の場合、Add
は文字列リテラルから一時的な FString
を作成し、その後で一時的なコンテンツをコンテナ内の新しい FString
に移動しますが、Emplace
は文字列リテラルを使って直接 Emplace
を作成します。結果は同じですが、Emplace
は一時的な変数の作成を避けるので、FString
などの非トリビアルな value 型には適さない場合が多いです。
呼び出し側で一時的な変数が不要に作成され、コンテナへコピーあるいは移動されることを防ぐので、一般的に 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 element
Add
、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 つの関数は、CreateIterator
は要素の読み書きが可能、CreateConstIterator
は読み取り専用です:
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"]
この場合、文字列は長さによってソートされます。"Hello"、"Brave"、"World" という長さが同じの 3 つの文字列が、配列の位置に相対してどのように順序が変わっているかに注目してください。これは、Sort
が不安定で、同等の要素 (述語は長さのみ比較するので、これらの文字列はここでは同等) の相対的な順序が保証されないためです。Sort
はクイックソートとして実装されます。
バイナリ述語の有無に関係なく、HeapSort
関数はヒープソートの実行に使用します。Sort 関数を選択するかどうかは、特定のデータ、および特定のデータがが 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 == 6
配列メモリに直接アクセスする必要がある場合、C-style API との相互運用性のために、GetData()
関数を使って配列中の要素へポインタを返すことができます。このポインタは、配列が存在し、配列を変化させる操作が行われるまでは有効です。StrPtr
からの最初の Num()
インデックスのみ間接参照が可能です。
FString* StrPtr = StrArr.GetData();
// StrPtr[0] == "!"
// StrPtr[1] == "of"
// ...
// StrPtr[5] == "Tomorrow"
// StrPtr[6] - undefined behavior
コンテナが const であれば、返されるポインタも const になります。
要素の大きさをコンテナに問い合わせることもできます。
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
演算子[]
は参照を返します。従って、配列内で要素を可変するために使用することもできます。配列が const ではないと仮定します。
StrArr[3] = StrArr[3].ToUpper();
// StrArr == ["!","of","Brave","HELLO","World","Tomorrow"]
GetData 関数と同様に、演算子[]
は配列が const であれば const reference を返します。Last
関数を使って、インデックスを配列末尾から逆方向に開始することも可能です。インデックスのデフォルトはゼロに設定されています。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
}
これにより、最初に検出された要素のインデックスを Index
に設定します。要素が重複していて、最後の要素のインデックスを探したい場合は、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_NONE
IndexOfByKey
も同様に機能し、さらに任意のオブジェクトと要素の比較が可能です。Find
関数を使うと、検索開始前に引数が実際に要素の型 (このケースでは FString
) に変換されます。IndexOfByKey
関数の場合はキーを直接比較するので、key 型が直接要素の型に変換できない場合でも検索をサポートします。
IndexOfByKey
は 演算子==(ElementType, KeyType)
が存在するすべてのキー型に対して機能します。IndexOfByKey
は最初に検出された要素のインデックスを返し、要素が検出されなかった場合は INDEX_NONE
を返します。
int32 Index = StrArr.IndexOfByKey(TEXT("Hello"));
// Index == 3
指定された述語と一致する最初の要素のインデックスを探すために IndexOfByPredicate
関数を使います。何も検出されなければ、ここでも特別な 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 == nullptr
FindByPredicate
は 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');
});
Removal
関数の 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 つだけ含むことができない場合の最適化に有用です。
ValArr.RemoveSingle(30);
// ValArr == [10,5,10,15,25,30]
RemoveAt
関数を使用して、インデックス番号を指定して要素を取り除くこともできます。無効のインデックスをこの関数に渡すとランタイム エラーが発生するので、IsValidIndex
を使って配列に提供する予定のインデックスをもつ要素があるか検証すると良いです。
ValArr.RemoveAt(2); // インデックス 2 のエレメントを削除します
// ValArr == [10,5,15,25,30]
ValArr.RemoveAt(99); // これによりランタイムエラーが発生します
// インデックス 99 にエレメントがないためです。
RemoveAll
関数を使って、述語と一致する要素を削除することもできます。例えば、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]
ValArr2.RemoveAllSwap([](int32 Val) {
return Val % 3 == 0;
});
// ValArr2 == [1,4,4]
最後に、Empty
関数は配列からすべてを除去します。
ValArr2.Empty();
// ValArr2 == []
演算子
配列は一般的な value 型です。標準のコピー コンストラクタもしくは代入演算子でコピーすることができます。配列は必ず要素を所有しているため、配列は「深いコピー」が行われ、新規の配列にはその要素の個々のコピーが作られます。
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
for (auto& Str : FlavorArr2)
{
Str = Str.ToUpper();
}
// FlavorArr2 == ["CHOCOLATE","VANILLA"]
bool bComparison2 = FlavorArr1 == FlavorArr2;
// bComparison2 == true, because FString comparison ignores case
Exchange(FlavorArr2[0], FlavorArr2[1]);
// FlavorArr2 == ["VANILLA","CHOCOLATE"]
bool bComparison3 = FlavorArr1 == FlavorArr2;
// bComparison3 == false, because the order has changed
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 つの違いは、HeapPop 関数が最上位の要素のコピーを返すために要素の型を参照するのに対して、HeapPopDiscard 関数は最上位のノードを返さずにそのまま削除します。いずれの関数の場合も配列への変更結果は同じで、他の要素の順序を適切に入れ替えることでヒープが維持されます。
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
Slack
配列はリサイズできるので、使用するメモリ量も変動します。要素を追加するたびにメモリを再割り当てしなくて済むように、アロケータは常に必要以上のメモリを確保しておくことで、 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
SlackArray.Add(2);
SlackArray.Add(3);
SlackArray.Add(4);
SlackArray.Add(5);
// SlackArray.GetSlack() == 17
// SlackArray.Num() == 5
// SlackArray.Max() == 22
再割り当て後のコンテナ内のスラック数はアロケータが決定します。従って、ユーザーはスラックに残っている定数に頼るべきではありません。
スラック管理は要件ではありませんが、配列を最適化するヒントに利用することができます。例えば、配列に要素を 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);
SlackArray.Add(3);
// SlackArray.GetSlack() == 0
// SlackArray.Num() == 3
// SlackArray.Max() == 3
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
// SlackArray.Max() == 4
Raw メモリ
結局のところ、TArray
はアロケートされたメモリのラッパーに過ぎません。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;
float Flt;
};
TArray<S> SArr;
SArr.AddZeroed();
// SArr == [{ Int: 0, Ptr: nullptr, Flt: 0.0f }]
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);
// SArr == [
// { Int: 0, Ptr: nullptr, Flt: 0.0f },
// { Int: 5, Ptr: 0x12345678, Flt: 3.14f },
// { Int: 2, Ptr: 0x87654321, Flt: 2.72f },
// { Int: 0, Ptr: nullptr, Flt: 0.0f },
// { Int: 0, Ptr: nullptr, Flt: 0.0f }
// ]
その他
BulkSerialize
関数は、要素別のシリアル化ではなく raw バイトのブロックとして配列をシリアル化するために 演算子<<
の代用として使用できるシリアル化関数です。ビルトイン型あるいはプレーンなデータ構造体など、要素の型がトリビアル型の場合、パフォーマンスが上がります。
CountBytes
関数と GetAllocatedSize
関数は、配列が使用中のメモリを概算します。これらの関数は一般的には統計情報の報告に使用されます。
Swap
関数と SwapMemory
関数は両方とも 2 つのインデックス番号を受け取り、これらのインデックス番号の要素の値をスワップします。これらは同等の関数ですが、Swap
関数がインデックス上で追加のエラーチェックを行い、インデックスが範囲外の場合アサートするという点が異なります。