1. 導入
C++でプログラミングをしていると、「リストやベクターの中から特定の条件に合う要素だけを削除したい」という場面によく遭遇します。しかし、ループの中で単純に要素を削除しようとすると、イテレータが無効化されてプログラムがクラッシュしたり、計算量が膨大になってパフォーマンスが低下したりすることがあります。この問題をスマートに解決するのが、今回紹介する「Erase-Removeイディオム」と、C++20から登場した「std::erase_if」です。
2. 基礎知識
まず、なぜ単純な削除が難しいのかを理解しましょう。std::vectorのようなコンテナは、メモリ上に要素が隙間なく並んでいます。途中の要素を削除すると、それ以降のすべての要素を前方にずらす必要があり、これをループの中で行うと処理効率が非常に悪くなります。
「Erase-Removeイディオム」とは、以下の2段階で処理を行う手法です。
1. Remove: 残したい要素をコンテナの先頭側に集め、削除対象の要素を末尾側に追いやる(この時点ではコンテナのサイズは変わりません)。
2. Erase: コンテナのサイズを変更し、末尾に追いやられた不要な要素を実際に削除する。
3. 実装/解決策
C++20以前は、この「Remove」と「Erase」を組み合わせて記述する必要がありました。しかし、C++20からは std::erase_if という便利な非メンバ関数が登場し、これらの一連の操作を一行で安全に実行できるようになりました。内部的には、条件に合致しない要素を前方へムーブ代入して詰め直すため、無駄なメモリ確保が発生せず、非常に効率的(計算量O(N))です。
4. サンプルプログラム
以下のコードは、ベクターから偶数を削除する実例です。C++20以降の環境でコンパイルしてください。
include
include
include
int main() {
std::vector
// C++20からの書き方: std::erase_if
// これだけで条件に合う要素をすべて削除し、サイズも適切に調整されます
std::erase_if(numbers, [](int x) {
return x % 2 == 0; // 偶数を削除する条件
});
// 結果の表示
std::cout << "削除後の要素: ";
for (int n : numbers) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
5. 応用・注意点
現場での開発において、いくつか注意すべきポイントがあります。
std::erase_ifが使えない環境の場合
古いC++標準(C++17以前)を使用している場合は、従来の「Erase-Removeイディオム」を記述する必要があります。具体的には vec.erase(std::remove_if(vec.begin(), vec.end(), 条件式), vec.end()); と書きます。この書き方は非常に有名ですが、書き間違いが起きやすいため注意しましょう。
イテレータの無効化に注意
ループ処理中に自分でイテレータを操作して削除を行うと、指している先が消えてしまい、思わぬバグを生みます。コンテナの要素を削除したいときは、なるべく今回紹介したstd::erase_ifのような標準アルゴリズムに任せるのが、最も安全でバグの少ない手法です。
モダンなC++開発では、こうした標準ライブラリの便利な機能を活用することで、コードの可読性とパフォーマンスを両立させることができます。ぜひ活用してみてください。

コメント