【问题标题】:remove_if based on vector index with a functorremove_if 基于带有仿函数的向量索引
【发布时间】:2015-12-24 19:10:46
【问题描述】:

这个question 展示了如何使用函数谓词基于向量索引来使用erase/remove_if。这在第一次调用函数时效果很好,但是因为局部静态变量保持状态,所以在下一次调用不同的向量时我会不走运。所以我想我可以使用一个带有可重用私有变量的函子。除了第一个元素外,它主要工作。 remove_if 使用函子的方式有一些特殊之处,会弄乱私有变量的初始化

#include <vector> 
#include <algorithm>
#include <iostream>
#include <iterator>

using namespace std;

class is_IndexEven_Functor {
public:
  is_IndexEven_Functor() : k(0) {}

  bool operator()(const int &i) {
    cout << "DEBUG: isIndexEvenFunctor: k " << k << "\ti " << i << endl; ////

    if(k++ % 2 == 0) {
      return true;
    } else {
      return false;
    }
  }
private:
  int k;
};

int main() {

  is_IndexEven_Functor a;
  a(0);
  a(1);
  a(2);
  a(3);

  vector<int> v;
  v.push_back(0);
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);

  cout << "\nBefore\n";
  copy(v.begin(), v.end(), ostream_iterator<int>(cout, " ")); cout << endl;

  is_IndexEven_Functor b;
  v.erase( remove_if(v.begin(), v.end(), b), v.end() );

  cout << "\nAfter\n";
  copy(v.begin(), v.end(), ostream_iterator<int>(cout, " ")); cout << endl;

  return 0;
}

这是输出:

DEBUG: isIndexEvenFunctor: k 0  i 0
DEBUG: isIndexEvenFunctor: k 1  i 1
DEBUG: isIndexEvenFunctor: k 2  i 2
DEBUG: isIndexEvenFunctor: k 3  i 3

Before
0 1 2 3 

DEBUG: isIndexEvenFunctor: k 0  i 0
DEBUG: isIndexEvenFunctor: k 0  i 1  // why is k == 0 here ???
DEBUG: isIndexEvenFunctor: k 1  i 2
DEBUG: isIndexEvenFunctor: k 2  i 3

After
2 

问题的关键是为什么在第二次调用仿函数时k 的值等于 0(以及如何解决它)? 我猜这与 remove_if 将其用作临时对象或其他东西有关,但我真的不明白这意味着什么。

编辑:如果我能避免 c++11 那就太好了

【问题讨论】:

    标签: c++ vector stl remove-if


    【解决方案1】:

    是的,允许实现复制函数,因此如果函数具有可变状态,您可能会感到困惑。有几种方法可以解决这个问题。最简单的可能是使用std::reference_wrapper,可以使用std::ref 函数创建:

    is_IndexEven_Functor b;
    v.erase( remove_if(v.begin(), v.end(), std::ref(b)), v.end() );
    

    现在,实现不是复制函数对象,而是复制一个包装器,因此只有一个原始函数对象的实例。

    另一种选择是将索引与函数分开:

    class is_IndexEven_Functor {
    public:
        is_IndexEven_Functor(int &index) : k(index) {}
    
        .
        .
        .
    private:
        int &k;
    };
    

    并像这样使用它:

    int index = 0;
    is_IndexEven_Functor b(index);
    v.erase( remove_if(v.begin(), v.end(), b), v.end() );
    

    【讨论】:

    • 谢谢,这适用于 c++11。 c++03 有解决方法吗?
    • @confusedCoder:我添加了一个示例。
    • @confusedCoder:你也可以很容易地制作自己的reference_wrapper
    【解决方案2】:

    一般来说,带状态的函子对于 STL 算法来说不是很好,因为它们不能保证函子的处理。不管怎样,它可以为每次迭代创建一个仿函数的新副本(从原始副本!)以执行检查。 STL 假定函子是无状态的。

    为了克服这个问题,您应该在仿函数内部使用对状态的引用(在您的情况下,让您的 k 引用之前初始化的 int)或在引用包装器中传递仿函数本身。要回答特定问题(即remove_if 中发生了什么),让我们看一下实现(当然是实现之一):

     __remove_if(_ForwardIterator __first, _ForwardIterator __last,
                    _Predicate __pred)
        {
          __first = std::__find_if(__first, __last, __pred);
          if (__first == __last)
            return __first;
          _ForwardIterator __result = __first;
          ++__first;
          for (; __first != __last; ++__first)
            if (!__pred(__first))
              {
                *__result = _GLIBCXX_MOVE(*__first);
                ++__result;
              }
          return __result;
        }
    

    如您所见,它使用 find_if 的谓词 COPY,然后从找到的位置继续使用原始谓词 - 但 original 对新位置一无所知,反映在副本中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-20
      • 2012-11-17
      • 2014-06-01
      • 1970-01-01
      相关资源
      最近更新 更多