C++のvectorで特定の要素を削除する際に用いるremove関数は、erase関数と組み合わせて使うと安全。
remove関数だけ用いる場合
remove関数は、指定した要素を取り除くことができる。
第一引数、第二引数でイテレータで範囲を指定して、第三引数に削除したい要素を指定する。
vector<int> vec = {1, 0, 2, 0, 3, 0, 4, 0};
auto newEnd = remove(vec.begin(), vec.end(), 0);
for(auto it=vec.begin(); it != newEnd; ++it){
cout << *it << " "; // 1 2 3 4
}
通常、remove関数の戻り値が取り除いた後の有効領域の末尾の次を指すイテレータを返すので、それを用いて続きの処理を行う。
remove関数の注意点
しかし、関数実行後のコンテナに注目してみると、実際には取り除かれた分が前に詰められるだけで、後ろの要素は無効領域として残ったままになる。
vector<int> vec = {1, 0, 2, 0, 3, 0, 4, 0};
remove(vec.begin(), vec.end(), 0); // {1, 2, 3, 4, 3, 0, 4, 0}
/* (「3, 0, 4, 0」の部分が無効領域として残る) */
つまり、取り除いた後もコンテナサイズ自体は変わらない。
コンテナサイズが変わっていないことに気づかずに、end関数や範囲for文を用いて処理を継続すると想定しないエラーを引き起こしてしまうかもしれない。
erase関数と組み合わせて使う
remove関数実行後の無効領域をerase関数で完全に削除しておくと安全。
vector<int> vec = {1, 0, 2, 0, 3, 0, 4, 0};
auto newEnd = remove(vec.begin(), vec.end(), 0);
vec.erase(newEnd, vec.end());
for(auto e: vec){ // 範囲for文も使用できる
cout << e << " "; // 1 2 3 4
}
こちらの方が、要素を取り除いた後の末尾のイテレータ・コンテナサイズが更新されるので、誤って無効領域にアクセスしてしまうことも未然に防ぐことができる。