【问题标题】:Can I remove elements from std::list, when I'm iterating on it?当我迭代它时,我可以从 std::list 中删除元素吗?
【发布时间】:2019-03-10 12:07:06
【问题描述】:

当我对它进行迭代时,我可以从 std::list 中删除元素吗?比如这样:

std::list<int> lst;
//....
for (std::list<int> itr = lst.begin(); itr != lst.end(); itr++)
{
    if (*itr > 10)
         lst.remove(*itr);
}

? 为什么?

【问题讨论】:

标签: c++ stl


【解决方案1】:

正确的代码如下:

for (std::list<int>::iterator itr = lst.begin(); itr != lst.end(); /*nothing*/)
{
    if (*itr > 10)
        itr = lst.erase(itr);
    else
        ++itr;
}

当您从列表中删除一个项目时,您可能会使迭代器无效(如果它指向被删除的项目)。因此您需要使用erase 删除(它返回一个指向下一个项目的有效迭代器)。

更好的主意是使用std::remove_if:

bool greater_than_10(int x)
{
    return x > 10;
}

lst.remove_if(greater_than_10);

如果你的编译器支持lambdas,你可以把它更短:

lst.remove_if([](int x){ return x > 10; });

(我没有测试这段代码,因为我的编译器不是那么新;幸运的是,lambda 函数是从@John Dibling 的答案中偷来的。)


实际上,从列表中删除会使 only the iterators pointing to the item being deleted 无效。但请注意,其他 STL 容器没有此属性。


因此,简而言之:一般来说,您不应该在迭代列表时从列表中删除项目,因为删除可能会使迭代器无效(并且程序可能会崩溃)。但是,如果您完全确定您删除的项目不是您在删除时使用的任何迭代器引用的值,您可以删除。

请注意,对于其他 STL 容器(例如向量),约束更加严格:从容器中删除不仅会使指向已删除项的迭代器失效,而且可能还会使其他迭代器失效!因此,在迭代容器时从这些容器中删除会更成问题。

【讨论】:

  • 哈。当两个 SO 人发布几乎相同的示例代码时,这一定是个好主意。
  • 哦,我举了一个不好的例子。我只想使用 remove 方法(如果特定条件,我想删除所有具有特定值的项目)。
  • @miksayer:你不能,因为删除项目可能会使迭代器失效,所以你的下一次迭代可能会失败。
  • erase-remove 惯用语适用于向量,而不适用于列表!
  • 我为remove_if添加了示例代码,希望你不要介意。另外,我将itr 的类型从std::list 固定为std::list&lt;int&gt;::iterator
【解决方案2】:

没有。示例代码使itr 无效,导致未定义的行为。但这会起作用:

for (std::list<int>::iterator itr = lst.begin(); itr != lst.end(); )
{
    if (*itr > 10)
        itr = lst.erase(itr);
    else
        ++itr;
}

【讨论】:

  • 预增迭代器是个好主意;我更改了我的代码以匹配此。现在示例代码完全一样:)
【解决方案3】:

不,你不能。

但是你可以(并且应该)使用std::remove_if 和一个“大于10”的函子,像这样:

#include <list>
#include <algorithm>


int main()
{
    std::list<int> lst;
    lst.push_back(1);
    lst.push_back(12);
    lst.push_back(1);
    //....
    lst.erase(std::remove_if(lst.begin(), lst.end(), std::bind2nd(std::greater<int>(), 10)), lst.end());
}

另一种更通用的方法是编写您自己的自定义函子。这是一个函子 is_a_match,如果要检查的值大于 10,则返回 true。您可以重新定义 operator() 以返回 true 以对应于在您的情况下“匹配”的任何含义:

#include <list>
#include <algorithm>
#include <functional>

struct is_a_match : public std::unary_function<int, bool>
{
    is_a_match(int val) : val_(val) {};
    bool operator()(int victim) const { return victim > val_; }
private:
    int val_;
};

int main()
{
    std::list<int> lst;
    lst.push_back(1);
    lst.push_back(12);
    lst.push_back(1);
    //....
    lst.erase(std::remove_if(lst.begin(), lst.end(), is_a_match(10) ));
}

如果您受益于符合 C++0x 的编译器,您还可以使用 lambda,这使得在许多情况下可以摆脱仿函数并编写更具表现力的代码

#include <list>
#include <algorithm>

int main()
{
    std::list<int> lst;
    lst.push_back(1);
    lst.push_back(12);
    lst.push_back(1);
    //....
    lst.erase(std::remove_if(lst.begin(), lst.end(), [](int v) {return v > 10;}));
}

【讨论】:

  • 其实list有自己的remove_if成员函数:lst.remove_if(std::bind2nd(std::greater&lt;int&gt;(), 10));
  • @Fred: 是的,但这更通用,并教如何为list 以外的集合执行此操作。我也更喜欢非成员函数,而不是 Scott Meyers 和扩展的一般思想。
  • erase-remove 习惯用法在列表上确实是个坏主意。它将单个元素的删除速度从 O(1) 减慢到 O(n)。不要那样做。 Beware the illusion of container-independent code.
  • @John:您真的考虑过 O(1) 与 O(n) 过早优化?你一定是在开玩笑吧。我们在 vectors 上使用擦除删除的确切原因是从 O(n^2) 下降到 O(n)。在速度方面,选择高效的算法比选择 C++ 而不是“较慢”的语言重要几个数量级。
  • 如果您在程序启动时创建一个包含 10 个整数的列表,是的,没关系。
【解决方案4】:

我想你可以,但你必须在移除元素后重新分配迭代器,这可以使用erasemethod 而不是remove 来完成。

否则会不安全,不应该这样做。

【讨论】:

    【解决方案5】:

    有关迭代器的说明,请参阅 http://www.cppreference.com/wiki/iterator/start

    几点说明:

    • 您应该使用前自增运算符 (++itr) 而不是后自增运算符 (itr++)
    • 失效取决于迭代器及其关联集合的确切实现。

    【讨论】:

      猜你喜欢
      • 2010-10-10
      • 2016-07-15
      • 2011-02-21
      • 1970-01-01
      • 2011-07-13
      • 2019-01-15
      相关资源
      最近更新 更多