1. 導入:なぜ「反復子の無効化」を知る必要があるのか
C++のSTL(標準テンプレートライブラリ)のコンテナは非常に便利ですが、使い方を誤るとプログラムが予期せずクラッシュしたり、壊れたデータを読み込んでしまうことがあります。その原因の一つが「反復子の無効化(Iterator Invalidation)」です。これは、コンテナの要素を操作した際、それまで使っていた「場所を示す情報(反復子や参照)」が突然無効になってしまう現象を指します。この仕組みを正しく理解することは、堅牢なC++プログラムを書くための第一歩です。
2. 基礎知識:反復子とメモリの仕組み
反復子(イテレータ)とは、コンテナ内の特定の要素を指し示す「ポインタのようなもの」です。
特に std::vector は、メモリ上に要素を隙間なく並べて保持します。しかし、要素を追加して容量がいっぱいになると、コンテナは「より広い場所」へ中身を丸ごと引っ越すことがあります。このとき、古い場所を指していた反復子は、存在しないメモリを指す「野良ポインタ(dangling pointer)」となってしまいます。これが無効化の正体です。
3. 実装/解決策:どうすれば安全か
最も重要なルールは「コンテナを操作した後は、古い反復子を信頼しない」ことです。
もしループ中に要素を削除・追加する必要がある場合は、操作の結果として返される「新しい反復子」で古いものを上書き更新するのが鉄則です。
4. サンプルプログラム
以下は、std::vector を使用して、安全に要素を削除する例です。
include <iostream>
include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// イテレータを使って要素を走査
auto it = vec.begin();
while (it != vec.end()) {
if (it % 2 == 0) {
// 偶数を削除する
// eraseは「削除した次の要素を指すイテレータ」を返すため、それで上書きする
it = vec.erase(it);
} else {
// 削除しなかった場合のみインクリメントする
++it;
}
}
for (int n : vec) {
std::cout << n << " "; // 結果: 1 3 5
}
return 0;
}
5. 応用・注意点:コンテナごとのルールの違い
すべてのコンテナで同じルールが適用されるわけではありません。
std::vector は要素の挿入や削除で頻繁に再配置が起こるため、無効化の影響を強く受けます。一方で std::list はノードをポインタで繋いでいるため、要素を削除しても他の要素を指す反復子は無効になりません。
現場での開発では、以下の点を意識してください。
・再確保の回避: 事前に reserve() を使って容量を確保しておけば、意図しない再配置を減らせます。
・ルールを覚える: コンテナごとに「どの操作でどの範囲の反復子が壊れるか」は決まっています。迷ったときは C++ リファレンスで「Iterator Invalidation」の項目を確認する癖をつけましょう。
この現象を理解しておくことで、デバッグが困難な「たまに落ちるプログラム」を未然に防ぐことができます。まずは今書いているコードで、イテレータを使い回していないかチェックしてみてください。

コメント