使用函数对象的向量索引移除remove_if

3
这个问题展示了如何使用基于函数谓词的erase/remove_if删除vector索引。这在第一次调用函数时很有效,但由于局部静态变量保持状态,在下一次调用不同的向量时会出现问题。因此,我想使用一个具有可重复使用私有变量的函数对象。它大部分工作正常,除了第一个元素。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个回答

6

是的,实现允许复制函数,因此如果函数具有可变状态,则可能会导致混乱的行为。有几种方法可以解决这个问题。最简单的方法可能是使用std::ref函数创建的std::reference_wrapper

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

现在,实现方式已经改为复制一个包装器(wrapper)而不是函数对象本身,因此原始函数对象只有一个实例。
另一种选择是将索引(index)与函数分离:
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:我已经添加了一个例子。 - Vaughn Cato
@confusedCoder:你也可以很容易地制作自己的reference_wrapper - Vaughn Cato

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 函数,然后从找到的位置继续使用原始谓词 - 但是原始谓词对于在副本中反映的新位置一无所知。


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