【问题标题】:Removing elements from vector using remove_if使用 remove_if 从向量中删除元素
【发布时间】:2019-01-07 09:51:47
【问题描述】:

我正在尝试使用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,当我在 cpp.sh 上运行它时,没有任何运行时错误的迹象,但是当我稍后在 Visual Studio 中尝试时,一个获取运行时错误指出了问题所在
  • 为什么是 remove_if?只要你要擦除,std::find 在效率方面要好得多。

标签: c++ c++11 vector stl


【解决方案1】:

您正在使用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());

【讨论】:

  • 这里的教训是单行是危险的,因为它们很难阅读。将std::remove_ifa.erase 的调用分开可以使代码更清晰。
【解决方案2】:

您添加了多余的括号,请将其更改为

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

注意comma operator 只返回最后一个操作数,这意味着您将a.end() 传递给erase,从而导致UB。

【讨论】:

    【解决方案3】:

    其他答案已经指出了问题所在。我想说的是,通过简化代码会更容易注意到这些类型的问题。

    我建议使用:

    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);
    }
    

    【讨论】:

      【解决方案4】:

      您在函数调用中的括号过多。

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

      只需在 std::remove_if 和通话结束时删除一个括号。

      【讨论】:

        【解决方案5】:

        你的问题是你正在做擦除删除成语内联。这很容易出错。

        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) );
        }
        

        这个小助手函数将擦除中容易出错的部分与其他噪音隔离开来。

        然后:

        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()
          )
        );
        

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

        然后我们将a.end() 传递给erase,这不是传递给erase 的有效迭代器。所以你的程序格式不正确,并且 UB 结果。

        这只是近似的原因。手动执行擦除删除时很容易犯很多错误;代码很脆弱,充满重复。

        DRY是你想要单点定制的原则,你不想重复不需要重复的东西。 erase_remove_if 是我尝试应用 DRY 来避免这种错误。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-06-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-07-22
          • 1970-01-01
          • 2014-05-05
          相关资源
          最近更新 更多