简体   繁体   中英

A C++ STL program using functor as predicate

In the following C++ STL program,I define a functor Nth and it returns true if it is revoke in the nth time.And I transform it to generic algorithm remove_if ,I get something strange.

The code:

#include <iostream>
#include <list>
#include <algorithm>
#include "print.hpp"

using namespace std;

class Nth{
private:
    int nth,ncount;
public:
    Nth(int n):nth(n),ncount(0){}

    bool operator()(int)
    {
        return ++ncount == nth;
    }
};

int main()
{
    list<int> col;
    for (int i = 1;i <=9 ;++i)
    {
        col.push_back(i);
    }

    PRINT_ELEMENTS(col,"col : ");

    list<int>::iterator pos;
    pos = remove_if(col.begin(),col.end(),
        Nth(3));

    col.erase(pos,col.end());

    PRINT_ELEMENTS(col,"nth removed : ");
}

print.hpp:

#include <iostream>

template <class T>
inline void PRINT_ELEMENTS (const T& coll, const char* optcstr="")
{
    typename T::const_iterator pos;

    std::cout << optcstr;
    for (pos=coll.begin(); pos!=coll.end(); ++pos) {
        std::cout << *pos << ' ';
    }
    std::cout << std::endl;
}

I run it in Microsoft Visual Studio 2008 and I get the result: 在此输入图像描述 It deletes the elements 3 and 6 that is not I want.I thought only 3 would be deleted. Could someone interpret for me?Thanks a lot.

From The C++ Standard Library: A Tutorial and Reference By Nicolai M. Josuttis

This happens because the usual implementation of the algorithm copies the predicate internally during the algorithm:

template <class ForwIter, class Predicate>
   ForwIter std::remove_if(ForwIter beg, ForwIter end,
                           Predicate op)
   {
       beg = find_if(beg, end, op);
       if (beg == end) {
           return beg;
       }
       else {
       ForwIter next = beg;
           return remove_copy_if(++next, end, beg, op);
       }
   }

The algorithm uses find_if() to find the first element that should be removed. However, it then uses a copy of the passed predicate op to process the remaining elements if any. Here, Nth in its original state is used again and it also removes the third element of the remaining elements, which is in fact the sixth element.

This behavior is not a bug. The standard does not specify how often a predicate might be copied internally by an algorithm. Thus, to get the guaranteed behavior of the C++ standard library you should not pass a function object for which the behavior depends on how often it is copied or called. Thus, if you call a unary predicate for two arguments and both arguments are equal, then the predicate should always yield the same result. That is, a predicate should not change its state due to a call, and a copy of a predicate should have the same state as the original. To ensure that you can't change the state of a predicate due to a function call, you should declare operator () as constant member function.

Don't use std::remove_if on a std::list . Instead, use the list's member function:

col.remove_if(Nth(3));

The generic algorithm rearranges the values of elements so that you can safely erase from the end, but for a list, the member algorithm removes the unwanted nodes directly without touching any other elements.

Update. As was pointed out, this isn't actually guaranteed to solve your problem, since your predicate is not allowed to have an internal by-value state. Try this instead:

struct Nth
{
    const int n;
    int & counter;
    Nth(int N, int & c) : n(N), counter(c) { }
    bool operator()(int) const { return ++counter == N; }
};

{
    int counter = 0;
    cols.remove_if(Nth(3, counter));
}

This new predicate is copyable and acts as a reference wrapper around your (external) counter variable.

I read the 《The Standard C++ Library》,and I find another solution.That is :re-implement the function remove_if:

template <class ForwIter,class Predicate>
ForwIter remove_if_re(ForwIter begin,ForwIter end,Predicate op)
{
    while(begin != end && !op(*begin))
        ++begin;
    if(begin == end)
        return begin;
    else{
        ForwIter next = begin;
        return remove_copy_if(++next,end,begin,op);
    }
}

It does work.

But I am a little curious.Does this implement not use a copy of the passed predicate op to process the remaining elements???

I am new to learn STL.I willappreciate for your patient answers.

Thanks a lot.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM