vector :: erase和std :: remove_if的奇怪行为,其结束范围与vector.end()不同

时间:2010-04-26 21:26:13

标签: c++ stl

我需要从std :: vector的中间删除元素。

所以我试过了:

struct IsEven {
    bool operator()(int ele)
    {
        return ele % 2 == 0;
    }
};

    int elements[] = {1, 2, 3, 4, 5, 6};
    std::vector<int> ints(elements, elements+6);

    std::vector<int>::iterator it = std::remove_if(ints.begin() + 2, ints.begin() + 4, IsEven());
    ints.erase(it, ints.end());

在此之后,我希望ints向量具有:[1,2,3,5,6]。

在Visual Studio 2008的调试器中,在std::remove_if行之后,ints的元素被修改,我猜我在这里遇到某种未定义的行为。

那么,如何从矢量范围中删除元素?

3 个答案:

答案 0 :(得分:13)

修改:抱歉,原始版本不正确。固定的。

这是发生了什么。您对remove_if的输入是:

1  2  3  4  5  6
      ^     ^
    begin  end

remove_if算法会查看beginend之间的所有数字(包括begin,但不包括end),并删除其间的所有元素匹配你的谓词。因此,在remove_if运行后,您的矢量看起来像这样

1  2  3  ?  5  6
      ^  ^
  begin  new_end

?是一个我认为不具有确定性的值,但如果保证它是4的任何值。并且new_end指向您提供的输入序列的新结尾 ,现在已删除匹配的元素,是std::remove_if返回的内容。请注意,std::remove_if不会触及您提供的子序列之外的任何内容。对于更广泛的示例,这可能更有意义。

说这是你的输入:

1  2  3  4  5  6  7  8  9  10
      ^              ^
    begin           end

std::remove_if之后,您会得到:

1  2  3  5  7  ?  ?  8  9  10
      ^        ^
    begin      new_end

想一想。它所做的是从子序列中删除4和6,然后将子序列中的所有向下移动以填充已删除的元素,然后将end迭代器移动到新的结束相同的子序列。目标是满足它生成的(beginnew_end]序列与您传入的(beginend]子序列相同的要求,但删除了某些元素。在传入的end之内或之外的任何内容都保持不变。

然后,你要删除的是返回的结束迭代器和你给它的原始结束迭代器之间的所有内容。这些是?“垃圾”值。所以你的擦除电话实际应该是:

ints.erase(it, ints.begin()+4);

erase的调用,您刚刚删除了执行删除的子序列末尾之外的所有内容,这不是您想要的内容。

使这一点复杂化的原因是remove_if算法实际上并没有在向量上调用erase(),或者在任何点改变向量的大小。它只是移动元素并在您要求它处理的子序列结束后留下一些“垃圾”元素。这看起来很愚蠢,但是STL这样做的全部原因是避免了doublep带来的无效迭代器的问题(并且能够在不是STL容器的东西上运行,比如原始数组)。 p>

答案 1 :(得分:1)

std::vector中的擦除元素使移除元素之外的迭代器无效,因此您不能使用接受范围的“外部”函数。你需要以不同的方式做到这一点。

编辑:

通常,您可以使用这样的事实:擦除一个元素会将所有元素“移动”到后面的其他位置。像这样:

for (size_t scan = 2, end = 4; scan != end; )
  {
     if (/* some predicate on ints[scan] */)
       {
         ints.erase (ints.begin () + scan);
         --end;
       }
     else
       ++scan;
  }

请注意,std::vector不适合删除中间的元素。如果你经常这样做,你应该考虑别的事情(std::list?)。

编辑2:

正如评论所阐明的那样,第一段并非如此。在这种情况下,std::remove_if应该比我在第一次编辑中建议的更有效,所以忽略这个答案。 (保留评论。)

答案 2 :(得分:1)

行为并不奇怪 - 你正在消除错误的范围。 std::remove_if将“删除”的元素移动到输入范围的末尾。在这种情况下,您正在寻找的是:

ints.erase(it, ints.begin() + 4 /* your end of range */);

从果壳中的C ++开始:

  

remove_if函数模板   “删除”pred返回的项目   来自[第一个,最后一个]范围的错误。   返回值是新的一个   范围结束。相对顺序   没有删除的项目是   稳定。

     

实际上没有任何东西被删除   底层容器;相反,项目   右侧分配给新的   这些位置让他们覆盖了   pred返回false的元素。   请参见图13-13(在remove_copy下)   有关删除过程的示例。

相关问题