使用remove_if从向量中删除元素

13

我正在尝试使用remove_if删除向量元素。但是没有成功。我做错了什么?

以下是我的代码:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

void printme(std::vector<int>& a){
    for(const auto& item: a)
    std::cout << item << std::endl;
}

int main()
{
    std::vector<int> a {1, 2, 3, 4, 5, 6};
    printme(a);  
    a.erase( (std::remove_if(a.begin(), a.end(), [](const int& x){
        return x == 2;
        }), a.end()));
    printme(a);
}

我的输出结果只有:

1 2 3 4 5 6

期望的输出结果是:

1 2 3 4 5 6 1 3 4 5 6


你没有收到运行时错误吗?你的代码越界了,因为vector::end()指向了你的向量之外。 - Headcrab
@Headcrab,当我在cpp.sh上运行它时,没有任何运行时错误的迹象,但是当我稍后在Visual Studio中尝试时,会出现运行时错误,指向问题所在。 - user8024280
为什么要用remove_if?只要你要删除,从效率角度考虑,std::find更好。 - Red.Wave
5个回答

23

您正在使用只接受一个迭代器参数的std::vector::erase()成员函数的重载版本。作为erase()的参数,您提供了迭代器a.end(),因为以下表达式:

(std::remove_if(a.begin(), a.end(), [](const int& x){ return x == 2; }), a.end()))

由于逗号操作符的影响,评估结果为 a.end()

传递给只接受单个迭代器的 erase() 重载版本的迭代器必须是可解引用的。然而,迭代器 a.end() 不是可解引用的,因此调用 erase() 将导致未定义行为


要使用接受两个迭代器的重载版本,请删除对 std::remove_if 调用周围的括号:

a.erase(std::remove_if(a.begin(), a.end(), [](const int& x){
        return x == 2;
        }), a.end());

11
这里的教训是一行代码很难阅读,容易出错。将 std::remove_ifa.erase 的调用分开可以使代码更加清晰易懂。 - Pete Becker

8
您正在添加不必要的括号,请将其更改为
a.erase( std::remove_if(a.begin(), a.end(), [](const int& x){
    return x == 2;
    }), a.end());

请注意逗号运算符只返回最后一个操作数,这意味着您正在将a.end()传递给erase,这会导致UB。

7
其他答案已经指出了问题所在。我想说的是,简化代码可以更容易地发现这类问题。
我建议使用:
int main()
{
   std::vector<int> a {1, 2, 3, 4, 5, 6};
   printme(a);  

   auto it = std::remove_if(a.begin(), a.end(), [](const int& x){ return x == 2; });
   a.erase(it, a.end());

   printme(a);
}

5
你的函数调用中有太多括号了。
a.erase(std::remove_if(a.begin(), a.end(), [](const int& x) {return x == 2;}), a.end());

在调用std::remove_if之前和结尾处删除一个括号即可。


4
您的问题在于您正在使用内联方式进行erase-remove惯用语。这种方法非常容易出错。
template<class C, class F>
void erase_remove_if( C&& c, F&& f ) {
  using std::begin; using std::end;
  auto it = std::remove_if( begin(c), end(c), std::forward<F>(f) );
  c.erase( it, end(c) );
}

这个小帮手函数在与其他噪声隔离的情况下完成了erase remove的容易出错部分。
然后:
a.erase( (std::remove_if(a.begin(), a.end(), [](const int& x){
    return x == 2;
    }), a.end()));

变成

erase_remove_if(
  a,
  [](const int& x){
    return x == 2;
  }
);

现在你的代码突然可以工作了。

现在的原因是你有一个笔误:

a.erase(
  (
    std::remove_if(
      a.begin(),
      a.end(),
      [](const int& x){
        return x == 2;
      }
    ),
    a.end()
  )
);

在这里,我扩展了行的结构。从上面可以看出,您只向erase传递了一个参数; 即a.end(),因为您在括号中传递了( some remove expression, a.end() )。这调用了逗号运算符:因此它运行了删除表达式(将元素2移动到末尾),然后丢弃了返回的迭代器并计算为a.end()

然后我们将a.end()传递给erase,这不是要传递给erase的有效迭代器。所以您的程序不合法,会导致未定义的行为。

这只是直接原因。手动执行erase-remove时,您可能会轻易犯许多错误;代码很脆弱且充满了重复。

DRY是您希望有单个自定义点的原则,并且您不想重复不需要重复的事情。erase_remove_if是我尝试应用DRY来避免正是这种错误的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接