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 が返すイテレータを使うループが適しています。選択条件が既知の位置または範囲なのか、値との一致なのか、ループ中に評価する述語なのかに応じて方法を選びます。