【问题标题】:std::remove_if - lambda, not removing anything from the collectionstd::remove_if - lambda,不从集合中删除任何内容
【发布时间】:2011-05-27 14:10:51
【问题描述】:

好吧,我想我在这里犯了一个愚蠢的错误。我有一个 DisplayDevice3d 列表,每个 DisplayDevice3d 都包含一个 DisplayMode3d 列表。我想从 DisplayDevice3d 列表中删除没有任何 DisplayMode3d 的所有项目。我正在尝试使用 Lambda 来执行此操作,即:

    // If the device doesn't have any modes, remove it.

  std::remove_if(MyDisplayDevices.begin(), MyDisplayDevices.end(),
   [](DisplayDevice3d& device) 
   { 
    return device.Modes.size() == 0; 
   }
  ); 

即使在 MyDisplayDevices 中的 6 个 DisplayMode3d 中,只有 1 个在其 Modes 集合中有任何 DisplayMode3d,但没有从列表中删除任何内容。

我在这里犯了什么重大错误?

编辑:

好吧,我的错误是我应该使用 MyDisplayDevices.remove_if 而不是 std::remove_if,但是下面的答案对于使用 std::remove_if 是正确的:p。

MyDisplayDevices.remove_if( [](DisplayDevice3d const & device) 
                            { 
                                return device.Modes.size() == 0; 
                            });

【问题讨论】:

  • 如果容器本身支持remove_if,那么一定要使用它。我相信 std::list 就是这种情况。对于不提供 remove_if 的容器,您可以将 std::remove_if 与容器的擦除成员函数结合使用。
  • @sellibitze 换句话说,老鼠药
  • Erasing elements from a vector 的可能重复项

标签: c++ c++11 lambda remove-if erase-remove-idiom


【解决方案1】:

你需要在remove_if返回的迭代器上调用erase,它应该是这样的:

auto new_end = std::remove_if(MyDisplayDevices.begin(), MyDisplayDevices.end(),
                              [](const DisplayDevice3d& device)
                              { return device.Modes.size() == 0; });

MyDisplayDevices.erase(new_end, MyDisplayDevices.end());

【讨论】:

    【解决方案2】:

    remove_if 不会从列表中删除任何内容,只是将它们移动到末尾。您需要与erase 一起使用它。有关详细信息,请参阅此question

    【讨论】:

    • 所以我从迭代器中删除它返回到列表的末尾?
    • @Troyseph:你想保留的东西被移到前面(通过分配)。没有任何东西被移到后面。你不再需要的东西就在最后,可以删除。
    【解决方案3】:

    remove_if 不执行大小调整,而是将迭代器返回到最后一个未删除元素之后的元素。可以将此迭代器传递给erase() 进行清理。

    【讨论】:

      【解决方案4】:

      正如其他人所提到的,有办法让它发挥作用。但是,我的建议是完全避免 remove_if 并坚持使用标准的基于迭代器的删除。下面的成语适用于listvector,不会产生意外行为。

      for( vector<TYPE>::iterator iter = vec.begin() ; iter != vec.end() ; )
        if( iter->shouldRemove )
          iter = vec.erase( iter ) ; // advances iter
        else
          ++iter ; // don't remove
      

      正如下面的 cmets 所提到的,当移除超过 1 个元素时,此方法确实比 remove_if 具有更高的成本。

      remove_if 的工作原理是从向量中更靠前的位置复制元素,并用紧靠其前面的那个覆盖应该从向量中删除的向量。例如:remove_if 调用一个向量来移除所有 0 个元素:

      0 1 1 0 1 0
      

      结果:

      1 1 1 0 1 0
      

      请注意向量是如何不正确的。那是因为remove_if 返回一个指向最后一个有效元素的迭代器......它不会自动调整向量的大小。您仍然需要在调用 remove_if 返回的迭代器上调用 v.erase()

      下面是一个例子

      #include <stdio.h>
      #include <vector>
      #include <algorithm>
      #include <functional>
      using namespace std;
      
      void print( vector<int> &v )
      {
        for( int i : v )
          printf( "%d ", i );
        puts("");
      }
      
      int main()
      {
        vector<int> v = { 0, 1, 1, 0, 1, 0 };
        print( v ); // 0 1 1 0 1 0
        vector<int>::iterator it = remove_if( v.begin(), v.end(), [](int i){ return i == 0; } );
        print( v ); // 1 1 1 0 1 0
        v.erase( it, v.end() ); // actually cut out values not wanted in vector
        print( v ); // 1 1 1 (correct)
      }
      

      【讨论】:

      • 为什么你会推荐这个方法而不是 remove_if()?当然 remove_if() 也不会产生“意外行为”(它只是命名不当 =P)。而 std::remove_if() 会为编译器提供更多智能优化的机会,不是吗?因为它从头到尾迭代,同时向编译器保证不会发生任何有趣的事情,这与手动迭代不同。 (即 range-for() 与常规 for() 相比具有相同的优化优势)
      • bobobobo:问题是你的算法对于向量来说很慢。每个erase 会将剩余元素向下移动一个。如果您要擦除 1000 个元素中的 50 个元素,那就是大约 50,000 次移动,而您只需要幸存者移动大约 1000 次移动到他们的最后一个位置。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-05-01
      • 2018-06-07
      • 2013-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多