remove_if на основе векторного индекса с функтором

Этот вопрос показал, как использовать стирание/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, использующим его как временный объект или что-то в этом роде, но я действительно не понимаю, что это значит.

EDIT: было бы здорово, если бы я мог избежать С++ 11


person confusedCoder    schedule 24.12.2015    source источник


Ответы (2)


Да, реализации разрешено копировать функцию, поэтому вы можете получить запутанное поведение, если функционал имеет изменяемое состояние. Есть несколько способов обойти это. Вероятно, проще всего использовать 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() );
person Vaughn Cato    schedule 24.12.2015
comment
Спасибо, это отлично работает с С++ 11. Есть ли обходной путь для С++ 03? - person confusedCoder; 25.12.2015
comment
@confusedCoder: я добавил пример. - person Vaughn Cato; 25.12.2015
comment
@confusedCoder: Вы также можете довольно легко создать свой собственный reference_wrapper. - person Vaughn Cato; 25.12.2015

Как правило, функторы с состоянием не так хороши для алгоритмов 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, а затем продолжает исходный предикат из найденной позиции, но оригинал ничего не знает о новой позиции, отраженной в копии.

person SergeyA    schedule 24.12.2015