Language:
Page Info
Engine Version:

TArray:アンリアル エンジンの配列

主要なコンテナ クラスは TArray です。TArray は、同じ型を持つ他のオブジェクト (要素と呼びます) のシーケンスのオーナーシップと構成を定義するクラスです。TAarray はシーケンスで、要素の順序は明確に定義され、その関数を使ってこれらのオブジェクトと順序を確定的に操作します。

TArray

TArray は、アンリアル エンジンで最も一般的なコンテナ クラスです。高速で、メモリ効率がよく、安全に作成されています。TArray 型は 2 つのプロパティで定義されています。要素の型がメインで、アロケータがオプションです。

要素の型は、配列に格納されるオブジェクトの型です。TArray は、いわゆる均一なコンテナです。つまり、すべての要素が完全に同じ型です。要素の型を混在させることはできません。

アロケータは多くの場合省略され、ほとんどのユースケースに適切なアロケータがデフォルト設定になります。メモリでのオブジェクトの置き方や、さらに多くの要素を収容できるように、配列の拡大方法を定義します。デフォルトと違うビヘイビアを使いたい、もしくは自分で書き出すことができると感じた場合は、他にも様々なアロケータを使用できます。これについては、後述します。

TArray は value 型です。つまり、int32 や浮動小数点などの他のビルドイン型と同様に処理されます。継承はされないので、新規 / 削除を使ってヒープ上で TArray を作成 / 破棄することは通常はありません。要素も value 型で、コンテナに所有されます。配列そのものが破棄された時に破棄されます。他のものから TArray 変数を作成すると、その要素は新しい変数へコピーされます。ステートが共有されることはありません。

配列の作成と追加

次のように定義して、配列を作成します。

TArray<int32> IntArray;

整数のシーケンスを持つように設定された空の配列が作成されます。要素の型には、int32、FString、 TSharedPtr などの一般的な C++ 値ルールに従ってコピーが可能で破棄することができれば、どんな値でも使うことができます。アロケータは指定されていないので、 TArray は通常のヒープ割り当てになります。この時点では、メモリは割り当てられていません。

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 関数と Emplace 関数は、次の点だけが若干異なります。

  • Add は要素の型のインスタンスを配列にコピー (移動) します。

  • Emplace は、要素の型のインスタンスを新規作成するために引数を使います。

従って、TArray<FString> の場合、Add は文字列リテラルから一時 Fstring を作成し、その後で一時コンテンツをコンテナ内の新しい Fstring に移動しますが、Emplace は文字列リテラルを使って直接 Fstring を作成します。結果は同じですが、Emplace は一時的なものの作成を避けるので、FString などの非トリビアルな value 型には適さないこと場合が多いです。Push も Add と同じように使用することができます。

呼び出し側で不必要に一時的なものが作成されてコンテナへコピーあるいは移動されないようにするので、通常、Add より Emplace の方が望ましいです。ざっくり分けると、Add は トリビアル型、Emplace はそれ以外のものに使います。Emplace は Add よりも効率が低くなることはありませんが、Add の方が読み込みが良い場合があります。

Append により、複数の要素を別の TArray または pointer+size のいずれかから通常の 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 ("!" が既に存在しているため、StrArr は変わりません) 

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 ! "

Regular index-based iteration is also possible of course:

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 関数を使用できます。HeapSort 関数を使用するかどうかは、特定のデータおよびそれが Sort 関数と比べた効率性によります。HeapSort は Sort のように安定はしていません。上の例で 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) に対して存在するすべての key 型に機能し、これがその後の比較に使用されます。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 関数は、渡された要素と同等のすべての要素を消去します。

StrArr.Remove(TEXT("hello"));
// StrArr == ["!","of","Brave","World","Tomorrow"]
StrArr.Remove(TEXT("goodbye"));

// StrArr is unchanged, as it doesn’t contain "goodbye" (StrArr は "goodbye" を含まないので変化しません)

"hello" の削除を指示した場合ですら、"HELLO" が削除されていることに注目してください。要素の型の演算子 == によって等価がテストされます。FString の場合は、大文字・小文字を区別しない比較になります。

配列の最後の要素は、Pop 関数で取り除くことができます。

StrArr.Pop();

// StrArr == ["!", "of", "Brave", "World"]

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 関数を使用して、インデックス番号を指定して要素を取り除くこともできます。インデックス番号が必ず必要です。ない場合は、ランタイム エラーが発生します。インデックスはゼロから始まることを覚えておきましょう。

ValArr.RemoveAt(2); // Removes the element at index 2 (インデックス 2 で要素を取り除きます) 
// ValArr == [10,5,15,25,30]
ValArr.RemoveAt(99); // This will cause a runtime error as

                       // there is no element at index 99 (index 99 に要素が存在しないのでランタイム エラーが発生します)

RemoveAll 関数を使って、述語と一致する要素を削除することもできます。例えば、3 の乗数となるすべての値を削除するには以下のようになります。

ValArr.RemoveAll([](int32 Val) {
    return Val % 3 == 0;
});
// ValArr == [10,5,25]

これらすべてのケースでは、要素が削除されて配列内にできた「隙間」を埋める必要があるので、その後に続く要素を低いインデックス番号へずらしました。

シャッフル処理にはオーバーヘッドが伴います。残りの要素の順序にこだわらない場合は、RemoveSwapRemoveAtSwapRemoveAllSwap 関数によって、このオーバーヘッドを減らすことができます。これらの関数は、残りの要素の順序を保証しない点を除いて、スワッピングしないものと同じ動作をするので、より効率的に実行されます。

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 (順序が変わったので bComparison3 == false です)

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]

ツリーを視覚化すると下記のようになります。

image alt text

ツリー内のノードは、ヒープ化された配列内の要素の順序通りに、左から右、上から下へ読み取られます。配列は必ずしもヒープ内へ移動された後にソートされるわけではありません。ソートされた配列は有効なヒープにもなります。ヒープ構造定義は緩いので、同じ要素群に対して複数の有効なヒープを許可することができます。

HeapPush 関数を使って、他のノードの順序を変更してヒープを維持しつつ、要素をヒープに追加することができます。

HeapArr.HeapPush(4);
// HeapArr == [1,2,4,3,4,5,8,10,7,9,6]

image alt text

ヒープの最上位にあるノードを削除するには、HeapPop 関数と HeapPopDiscard 関数を使います。この 2 つの違いは、HeapPop 関数が最上位の要素のコピーを返すために要素の型を参照するのに対して、HeapPopDiscard 関数は最上位のノードを返さずにそのまま削除します。いずれの関数の場合も配列への変更結果は同じで、他の要素の順序を適切に入れ替えることでヒープが維持されます。

int32 TopNode;
HeapArr.HeapPop(TopNode);
// TopNode == 1
// HeapArr == [2,3,4,6,4,5,8,10,7,9]

image alt text

HeapRemoveAt は所定のインデックス番号で配列から要素を削除し、要素の順序を入れ替えてヒープを維持します。

HeapArr.HeapRemoveAt(1);
// HeapArr == [2,4,4,6,9,5,8,10,7]

image alt text

HeapPush、HeapPop、HeapPopDiscard、HeapRemoveAt は、例えば Heapify() の呼び出し、その他のヒープ操作、手動で配列をヒープに入れるなど、構造体が既に有効なヒープである場合のみ呼び出されることに注意してください。

さらに、これらの各関数は、Heapify を含めて、オプションでバイナリ述語を使ってヒープ内のノード要素の順序を決定できます。述語を使用する場合、正しい構造体を維持するために、すべてのヒープ操作において同じ述語を使用します。述語を何も指定しない場合、ヒープ操作では要素の型の 演算子< を使って順序を決定します。

最後に、ヒープの最上位ノードは、HeapTop を使えば配列を変更することなく確認できます。

int32 Top = HeapArr.HeapTop();
// Top == 2

Slack

配列はリサイズできるので、使用するメモリ量も変動します。要素を追加するたびにメモリを再割り当てしなくて済むように、アロケータは常に必要以上のメモリを確保しておくことで、追加の呼び出しによる再アロケートでパフォーマンスが低下しないようにしておきます。同様に、要素を削除しても通常はメモリは解放されません。コンテナ内の要素数と、次のアロケーションまでに追加できる要素数の差を スラック といいます。

デフォルト構造の配列にはメモリがアロケートされていないので、スラックは最初はゼロです。GetSlack 関数を使って、配列内のスラック数を調べます。もしくは、コンテナが再割り当てを行うまでの間に配列が保有できる要素の最大数を Max 関数で取得できます。GetSlack() is equivalent to 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 関数で削除して、要素自体を実際に削除せずに、要素の現在のシーケンスの維持に必要なだけの大きさにリサイズすることができます。

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 は常に持っている情報で最大限の実力を発揮しようとしますが、レベルを下げる必要がある場合があります。

これらの関数によりコンテナを有効なステートにして、間違いをした時に未定義の動作を起こすことができます。これらの関数の呼び出し後にコンテナを有効なステートに戻すかどうかは自分で決めることができますが、他の一般的な関数が呼び出される前までに行います。

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  }
// ]

Uninitialized 関数も Zeroed 関数も、使用には注意が必要です。コンストラクションが必要なメンバを含むために要素の型を修正した場合、あるいは有効なビット単位のゼロステートがない場合、配列要素は無効となり、動作が未定義になります。これらの関数は、FMatrix や FVector などの変更することがほぼないタイプの配列上での使用で非常に役立ちます。

その他

BulkSerialize 関数は、要素別のシリアル化ではなく raw バイトのブロックとして配列をシリアル化するために 演算子<< の代用として使用できるシリアル化関数です。ビルトイン型あるいはプレーンなデータ構造体など、要素の型がトリビアル型の場合、パフォーマンスが上がります。

CountBytes 関数と GetAllocatedSize 関数は、配列が使用中のメモリを概算します。CountBytes は FArchive を受け取り、GetAllocatedSize は直接呼び出すことが可能です。これらは一般的には統計情報の報告に使用されます。

Swap 関数と SwapMemory 関数は両方とも 2 つのインデックス番号を受け取り、これらのインデックス番号の要素の値をスワップします。これらは同等の関数ですが、Swap 関数がインデックス上で追加のエラーチェックを行い、インデックスが範囲外の場合アサートするという点が異なります。