HOWTO · C++
C++ でベクトルから要素を削除
この記事では、C++ でベクトルから要素を削除する方法を示します。
このページの内容
既知の位置または範囲を削除するには、std::vector::erase を呼び出します。C++20 で値に等しい要素をすべて削除するには std::erase を使います。それ以前の C++ 標準では、std::remove と erase を組み合わせる erase-remove イディオムを使います。各方法でベクターがどのように変わるか、イテレータを有効な状態に保つ方法を例で説明します。
std::vector::erase で1つの要素を削除する
std::vector::erase は、1つの要素を指すイテレータ、または半開区間 [first, last) を受け取ります。この範囲には first が含まれ、last は含まれません。単一要素を削除するオーバーロードには、要素を指す有効なイテレータが必要です。削除した範囲の次の要素を指すイテレータを返し、後続の要素がなければ end() を返します。
次の完全な例では、インデックス2の要素を削除し、返されたイテレータを読み取ります。
この例を実行してください。
このセクションのコードと表示されるラベルや出力は英語のままです。
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers{10, 20, 30, 40, 50};
auto next = numbers.erase(numbers.begin() + 2);
for (std::size_t i = 0; i < numbers.size(); ++i) {
if (i != 0) std::cout << ' ';
std::cout << numbers[i];
}
std::cout << "\nNext value: " << *next << '\n';
}
10 20 40 50
Next value: 40
要素を削除すると、削除位置以降を指すイテレータと参照は無効になります。それらを再び使う前に更新するか、破棄してください。範囲を削除するときは、範囲の先頭を指すイテレータと、末尾の次を指すイテレータを渡します。返されるイテレータと無効化の規則は同じです。削除範囲が末尾まで達する場合、返されるイテレータは numbers.end() と等しく、参照を外して値を読み取ることはできません。
std::erase で一致する値をすべて削除する(C++20)
C++20 では、<vector> を通じて宣言される std::vector 用の非メンバー関数オーバーロード std::erase が追加されました。指定した値に等しい要素をすべて削除し、削除した要素数を返します。同じ値が複数ある場合もすべて削除されるため、値の一致で対象を選ぶときに便利です。それ以前の言語標準にはこのオーバーロードがありません。その場合は次の節の erase-remove イディオムを使います。
次の例では、C++20 としてコンパイルすると std::erase を使い、それ以前の標準では erase-remove イディオムに切り替えます。どちらの分岐でも 2 の出現箇所をすべて削除し、削除数を出力します。
このセクションのコードと表示されるラベルや出力は英語のままです。
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers{1, 2, 3, 2, 4, 2, 5};
std::size_t removed;
#if __cplusplus >= 202002L
removed = std::erase(numbers, 2);
#else
const auto old_size = numbers.size();
const auto new_end = std::remove(numbers.begin(), numbers.end(), 2);
numbers.erase(new_end, numbers.end());
removed = old_size - numbers.size();
#endif
std::cout << "Removed: " << removed << "\nRemaining: ";
for (std::size_t i = 0; i < numbers.size(); ++i) {
if (i != 0) std::cout << ' ';
std::cout << numbers[i];
}
std::cout << '\n';
}
Removed: 3
Remaining: 1 3 4 5
std::vector 用の std::erase オーバーロードは C++20 から使え、削除数を返します。それ以前の標準向けの分岐では erase-remove イディオムを使います。この方法は次の節で説明します。
erase-remove イディオムで一致する値を削除する
C++20 より前の標準では、<algorithm> の std::remove と、ベクターのメンバー関数 erase を組み合わせます。std::remove は一致しない値を、書き換え可能な範囲の先頭側へ移動し、新しい論理的な末尾を指すイテレータを返します。ベクターのサイズ自体は変更されないため、末尾の範囲を消去するまでは要素数は元のままです。返されたイテレータと元のベクターの end() を erase に渡して、コンテナのサイズを小さくします。
次の例では、この2段階の方法で 2 をすべて削除します。
このセクションのコードと表示されるラベルや出力は英語のままです。
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers{1, 2, 3, 2, 4, 5};
const auto new_end = std::remove(numbers.begin(), numbers.end(), 2);
numbers.erase(new_end, numbers.end());
for (std::size_t i = 0; i < numbers.size(); ++i) {
if (i != 0) std::cout << ' ';
std::cout << numbers[i];
}
std::cout << '\n';
}
std::remove に渡す範囲は、アルゴリズムが残す値を並べ替えるため、書き換え可能でなければなりません。コンテナが const イテレータによる読み取り専用アクセスを提供していても、書き換え可能なイテレータ範囲を利用できれば、このイディオムを使えます。
反復処理中に要素を安全に削除する
述語や別の条件に基づいて削除する要素を決める場合は、イテレータを使ったループを使います。削除した後は erase が返したイテレータを代入します。現在の要素を残す場合にだけイテレータを進めてください。削除されたイテレータは無効になるため、古いイテレータを進めたり参照したりすると安全ではありません。
次の例では、イテレータを有効に保ちながら偶数を削除します。
このセクションのコードと表示されるラベルや出力は英語のままです。
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers{1, 2, 3, 4, 5, 6, 7};
for (auto it = numbers.begin(); it != numbers.end();) {
if (*it % 2 == 0) {
it = numbers.erase(it);
} else {
++it;
}
}
for (std::size_t i = 0; i < numbers.size(); ++i) {
if (i != 0) std::cout << ' ';
std::cout << numbers[i];
}
std::cout << '\n';
}
値の一致に基づいて削除するなら C++20 では std::erase、それ以前の標準では erase-remove イディオムを使います。条件に基づいて削除する場合は、erase が返すイテレータを使うループが適しています。選択条件が既知の位置または範囲なのか、値との一致なのか、ループ中に評価する述語なのかに応じて方法を選びます。