【UE5】範囲 for (ranged-for) の罠
今回は 範囲 for (ranged-for, ranged-based for loop) で陥りやすい罠について書きます。
この罠は C++の言語仕様の問題なので Unreal Engine特有の問題ではありません。ただ、UEで TArray, TMap といったコンテナを扱うときに起きやすい問題なので改めて紹介します。
- 動作確認バージョン: UE5.5.4
範囲 for の基本と注意点
範囲 forとは
範囲 forとは、コンテナ(配列・マップなど)を先頭から末尾まで順に処理するための C++ の構文で、C++11にて導入されたものです。
for(auto& Elem : Container) {
// 各要素に対する処理
}重要な前提(C++標準)
早速結論なのですが、C++ の範囲 forでは、 ループ中にコンテナの構造を変更する行為は非常に危険です。
その理由は以下のとおり。
- コンテナ操作によって イテレータや参照が無効化された後にアクセスすると未定義動作となる
- 無効化されるかどうかは コンテナの種類・操作内容に依存する
そして UE5 については、この操作を行うと TArray / TMap / TSet で多くのケースでイテレータや参照が無効化される可能性が高い ため、 実務上は「範囲 for 中の追加・削除は NG」と考えた方が良さそうです。
危険な例
具体的なコード例としては、範囲 for のループの中で、Add(), Remove() に関する処理を呼び出してはいけません。
TArray<int32> Numbers = {1,2,3};
for (int32& N : Numbers) {
if (N == 2) {
Numbers.RemoveAt(0); // 危険:内部配列が変化
}
}UE5 の自動チェック機構
UE5 では ranged-for 使用時に、ループ開始時の要素数を記録してループ中に要素数が変化するとアサーションが発生します。
そのようなログが出力されるかどうかは環境によりますが、おおよそ以下の出力となります。
Ensure condition failed: this->Array.Num() == InitialNum [File:C:\Program Files\Epic Games\UE_5.5\Engine\Source\Runtime\Core\Public\Containers\SparseArray.h] [Line: 1032]
Container has changed during ranged-for iteration!
[2025.12.19-03.38.45:033][821]LogOutputDevice: Error: Ensure condition failed: this->Array.Num() == InitialNum [File:C:\Program Files\Epic Games\UE_5.5\Engine\Source\Runtime\Core\Public\Containers\SparseArray.h] [Line: 1032]
Container has changed during ranged-for iteration!InitialNumというのはイテレーション開始時の値で、それが配列要素数と一致しない、という判定で 「Container has changed during ranged-for iteration! (範囲 for中にコンテナが変更されました)」というメッセージが出力されます。
注意点として、このアサートが発生するのは Development / Debug ビルド時のみです。
Shippingビルドではそのまま実行されてしまいメモリ破壊など調査が困難な問題が発生することもあるため「アサートが発生するので、エラーになったら対応すれば良いか…」という考えではなく、この罠を意識してコードを書く必要があります。
安全な代替手段
以下、TArrayやTMapなどのコンテナで安全に要素を追加・削除する方法の例を紹介します。
①:別コンテナに集約
安全な追加・削除方法としては、イテレーター内で追加・削除を行わなわず、別のコンテナに要素だけ格納して別のループで追加や削除を行います。
TArray<int32> Src = {1, 2, 3, 4, 5};
TArray<int32> ToRemove; // 削除リスト.
for(int32 Val : Src) {
if (Val % 2 == 0) {
ToRemove.Add(Val); // 削除リストに追加.
}
}
// 実際の削除は削除リストのループで行う.
for(int32 Rem : ToRemove) {
Src.Remove(Rem);
}これが、最も安全で可読性が高い方法です。一目で「ちゃんと 範囲 for 対策をしているな」とわかります。
②:逆順インデックスループ
もう1つの方法が逆順インデックスループです。
TArray<int32> Values = {10, 20, 30, 40, 50};
// 逆順ループ.
for (int32 Idx = Values.Num(); Idx-- > 0;) {
if (Values[Idx] % 20 == 0) {
Values.RemoveAt(Idx);
}
}末尾からループすることで、削除によってコンテナの要素がズレる問題を回避できます。
これはインデックスずれを防止するお約束のパターンです。先頭からループを回したい場合は、削除時に インデックス (Idx) を "Idx--" と減らす方法もあります。
③:TArray::RemoveAll() を使う
TArray::RemoveAll() はラムダ式を渡して、bool (true) を返すことで配列から要素を削除する関数です。
TArray<int32> Src = {1, 2, 3, 4, 5};
Src.RemoveAll(
[](int32 Val) {
return (Val % 2) == 0; // true を返した要素が削除される
}
);bool を返すラムダ式を渡します。削除したい条件を判定し、削除する場合に true を返すと条件に合った要素が削除されます。
まとめ
まとめです。
- 範囲 for 中の構造変更は 未定義動作につながる
- UE5 は Development ビルドで検知するが Shipping では検知しないので要素数のずれによって予期せぬ動作やメモリ破壊が起きる
- TArray / TMap / TSet すべて同様に危険
- 安全策としては、①別コンテナへの集約を行う、②逆順インデックスループで安全に削除する、③TArray::RemoveAll()を使う
以上、範囲 for を適切に使うためにお役に立てればなによりです。

