在 C++17 中迭代时从 std::set 中删除一个元素

Removing an element from a std::set while iterating over it in C++17

我已阅读 this SO post, and this one too 关于 在迭代期间从 std::set 中删除元素。 然而,似乎在 C++17 中存在更简单的解决方案:

#include <set>
#include <iostream>
int main(int argc,char **argv)
{
    std::set<int> s;

    s.insert(4);
    s.insert(300);
    s.insert(25);
    s.insert(-8);

    for (auto it:s)
    {
        if (it == -8)
        {
            s.erase(it);
        }
    }
    std::cout << "s = {";
    for (auto it:s)
    {
        std::cout << it << " ";
    }
    std::cout << "}\n";
    return 0;
}

当我编译并运行一切顺利时:

$ g++ -o main main.cpp
$ ./main
s = {4 25 300 }

像这样擦除元素有什么注意事项吗?谢谢。

根据C++17标准:

9.5.4 The range-based for statement [stmt.ranged]

1 The range-based for statement

for ( for-range-declaration : for-range-initializer ) statement

is equivalent to

{
    auto &&__range = for-range-initializer ;
    auto __begin = begin-expr ;
    auto __end = end-expr ;
    for ( ; __begin != __end; ++__begin )
    {
        for-range-declaration = *__begin;
        statement
    }
}

所以 ,您的代码无效,因为您擦除迭代器当前指向的元素(std::set 同一个键只能有一个值!),因此迭代器失效并在 之后递增 ,这是未定义的行为。

请注意,您可以从集合中删除 另一个 元素,如 std::set(以及 std::mapstd::list)只有被擦除的迭代器无效,而所有其他迭代器仍然有效。

如果你打算移除一个容器的当前元素(包括std::vector,作为erase returns一个新的,有效的迭代器),你需要回退到一个经典的循环,如参考问题的 answer 所示;我个人喜欢以下的单行变体:

    iter = /*some condition*/ ? container.erase(iter) : std::next(iter);

如果您的 C++ 实现支持 Library Fundamentals TS(第二版),您可以:

#include <experimental/set>
#include <iostream>
#include <algorithm>
#include <experimental/iterator>
int main()
{
    std::set<int> s;

    s.insert(4);
    s.insert(300);
    s.insert(25);
    s.insert(-8);

    std::experimental::erase_if(s,
                                [](auto& key){ return key == -8; });
    std::cout << "s = {";
    std::copy(s.begin(), s.end(),
              std::experimental::make_ostream_joiner(std::cout, " "));
    std::cout << "}\n";
}