vectorの要素をループ中に削除するとイテレータが無効化されるバグ
std::vectorの要素をfor文の中でeraseしながら、そのままイテレータをインクリメントし続けてしまうバグです。eraseは削除した位置以降の要素を前に詰めるため、単純な++itでは次の要素を読み飛ばしてしまいます。
なぜエラーが出ないのか
出力: 4 1 3 (2番目の偶数4が消えずに残る)
(エラーなし)- C++は何も報告しません。コンパイルも実行も正常に完了するためです
出力: 4 1 3 (2番目の偶数4が消えずに残る)- 実際の挙動 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるか、コードを目で追うしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 #include <iostream> 2 #include <vector> 3 int main() { 4 std::vector<int> nums = {2, 4, 1, 3}; 5 for (auto it = nums.begin(); it != nums.end(); ++it ) { ^ 6 if (*it % 2 == 0) { 7 nums.erase(it); 8 } 9 } 10 for (int n : nums) std::cout << n << " "; 11 std::cout << std::endl; 12 return 0; 13 }
eraseは削除した位置以降の要素を前に詰め、削除した位置を指す新しいイテレータを返り値として返します。それを使わずに自分で++itすると、詰められて繰り上がってきた次の要素を読み飛ばしてしまいます。eraseの戻り値を使ってitを更新するのが正しい書き方です。
直し方: ++it を it = (*it % 2 == 0) ? nums.erase(it) : it + 1 にします。
パターン2
1 #include <iostream> 2 #include <vector> 3 int main() { 4 std::vector<int> vals = {6, 8, 1, 5}; 5 for (auto it = vals.begin(); it != vals.end(); ++it ) { ^ 6 if (*it % 2 == 0) { 7 vals.erase(it); 8 } 9 } 10 for (int v : vals) std::cout << v << " "; 11 std::cout << std::endl; 12 return 0; 13 }
「イテレータはコンテナの中身が変わっても位置を追いかけてくれる」というのは誤解です。vector::eraseは呼んだ時点でそのイテレータ自身を無効にします。
直し方: ++it を it = (*it % 2 == 0) ? vals.erase(it) : it + 1 にします。
パターン3
1 #include <iostream> 2 #include <vector> 3 int main() { 4 std::vector<int> nums = {10, 12, 3, 9}; 5 for (auto it = nums.begin(); it != nums.end(); ++it ) { ^ 6 if (*it % 2 == 0) { 7 nums.erase(it); 8 } 9 } 10 for (int n : nums) std::cout << n << " "; 11 std::cout << std::endl; 12 return 0; 13 }
単純なfor文の++itだけで削除を扱おうとすると1つ余分に読み飛ばします。vectorから複数条件で削除するときは、eraseの戻り値でイテレータを更新するか、std::remove_ifとの組み合わせ(消去・削除イディオム)を使うのが安全です。
直し方: ++it を it = (*it % 2 == 0) ? nums.erase(it) : it + 1 にします。
よくある誤解
「イテレータはコンテナの中身が変わっても位置を追いかけてくれる」というのは誤解です。vector::eraseは呼んだ時点でそのイテレータ自身を無効にし、代わりに次の要素を指す新しいイテレータを戻り値として返します。それを使わずに自分でインクリメントすると、1つ余分に読み飛ばしてしまいます。
まとめ
vectorの要素をループ中に削除するとイテレータが無効化されるバグは上級でつまずきやすい項目です。上の3パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。