remove_if基于矢量索引和仿函数

时间:2015-12-24 19:10:47

标签: c++ vector stl remove-if

这个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

那就太棒了

2 个答案:

答案 0 :(得分:6)

是的,允许实现复制该函数,因此如果函数具有可变状态,则可能会出现混乱行为。有几种方法可以解决这个问题。最简单的可能是使用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() );

答案 1 :(得分:4)

通常,具有状态的仿函数对于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,而不是继续使用找到位置的原始谓词 - 但是原始对新位置一无所知,反映在副本中。