C++におけるイテレータの無効化(iterator invalidation)とは?原因と対策を解説
本記事では、C++プログラミングにおいて重要な概念である「イテレータの無効化(iterator invalidation)」について詳しく解説します。
コンテナオブジェクトの要素をイテレータで走査している最中に、適切な境界チェックを行わないと、イテレータが無効化されてしまうことがあります。この現象は主に、コンテナオブジェクトの形状やサイズが変化することによって発生します。
たとえば vector に push_back() で要素を追加するとき、現在の容量(capacity)を超えると、vector全体が新しいメモリ領域へコピー(再配置)されます。このとき、既存のイテレータは古いメモリ位置を指したままになるため、未定義動作(undefined behavior)を引き起こす可能性があります。
サンプルコード
#include <bits/stdc++.h>
using namespace std;
int main() {
//vectorの宣言
vector <int> v{1, 5, 10, 15, 20};
//実行中にvectorを変更すると
//境界の無効化が発生する
for (auto it=v.begin();it!=v.end();it++)
if ((*it) == 5)
v.push_back(-1);
for (auto it=v.begin();it!=v.end();it++)
cout << (*it) << " ";
return 0;
}
実行結果
1 5 10 15 20 -1 -1
なお、このコードは環境によって異なる結果になる可能性があります。新しい要素を追加する際に vector が別のメモリ領域へ丸ごとコピーされ、イテレータが古い位置を指し続けると、エラー(未定義動作)が発生することがあるからです。実行結果が安定しないのはまさにこのためです。
イテレータの無効化を防ぐには?
イテレータの無効化によるバグは発見が難しく、深刻な問題につながることもあります。以下のような対策を心がけましょう。
reserve()で事前に容量を確保する: 再配置が発生しなければ、既存のイテレータは有効なまま維持されます。- ループ中にコンテナを変更しない: 要素の追加・削除は、走査とは別のタイミングで行うのが安全です。
- インデックスアクセスを検討する:
vectorなどランダムアクセス可能なコンテナでは、添字演算子を使うことで再配置の影響を受けにくくなります。 - 戻り値のイテレータを使う:
insert()やerase()などは有効なイテレータを返すため、それを利用して位置情報を更新します。
C++では、どの操作がイテレータを無効化するかはコンテナの種類(vector、list、map など)ごとに異なります。各コンテナの仕様を正しく理解し、安全なコードを書くことをおすすめします。
-
STLを使ったC++のvector(ベクトル)実装プログラム
std::vectorは、動的配列のように要素の挿入や削除に応じて自動的にサイズを変更できるコンテナで、メモリ(記憶域)の管理はコンテナ自身が行います。要素は連続したメモリ領域に配置されるため、イテレータを使ったアクセスや走査が可能です。また、先頭・中間・末尾のいずれの位置にもデータを挿入・削除できます。 使用する主な関数とその説明 v.size() : ベクトルのサイズ(現在の要素数)を返します。 v.push_back() : ベクトルの末尾に要素を挿入します。 v.pop_back() : ベクトルの末尾から要素を取り除きます。 v.capacity() : 現在ベクトルに
-
C++でベクトル(vector)を実装するプログラムの書き方
ベクトル(vector)とはC++のベクトル(vector)は、要素の挿入や削除に応じて自動的にサイズを変更できる動的配列です。ベクトルの要素は連続したメモリ領域(ストレージ)に格納され、その記憶域の管理はコンテナ側が自動的に行います。そのため、プログラマーは配列のサイズ管理を意識することなく、通常の配列のように手軽にデータを扱えます。以下に、ベクトルを実装したプログラムの例を示します。サンプルコード#include <iostream> #include <vector> #include <string> #include <cstdlib>