问题
Suppose we'd like to remove duplicate values from a vector of int
s. The usual solution is to sort the vector and erase duplicates with erase-remove idiom. But we need to mantain the order of the elements that will not be removed, so we can't sort. So one might come up with a predicate like this and use with with remove_if
algorithm:
struct comp {
std::set<int> s;
comp() : s() {}
bool operator()(int i)
{
return !(s.insert(i)).second;
}
};
But this will break if predicate object will be copied for some reason, since we'll get two copies of the set
member. And indeed, the gcc's implementation of remove_if
does exactly that:
template<typename _ForwardIterator, typename _Predicate>
_ForwardIterator
remove_if(_ForwardIterator __first, _ForwardIterator __last,
_Predicate __pred)
{
__first = _GLIBCXX_STD_A::find_if(__first, __last, __pred);
if(__first == __last) // ^^^^^ here a copy is made
return __first;
_ForwardIterator __result = __first;
++__first;
for(; __first != __last; ++__first)
if(!bool(__pred(*__first)))
{
*__result = _GLIBCXX_MOVE(*__first);
++__result;
}
return __result;
}
The workaround is to make set
member of our functor static:
struct comp {
static set<int> s;
comp() { s. clear(); }
bool operator()(int i)
{
return !(s.insert(i)).second;
}
};
set<int> comp::s;
But the question remains:
Do we need to make sure a possible copy of predicate functor will not break our logic? Is there anything in the standard that mandates (or prohibits) certain behaviour with regard to this issue? Or is it a bug in the implementation?
回答1:
Yes, the standard does not specify how many times the predicate will be copied, nor does it say in what order the predicate will be applied to elements of the container. Essentially, predicates must act like pure functions; they must have no observable state.1
So remove_if
does not sound like an appropriate algorithm here. Hacks such as storing the set
externally to the functor will not solve the problem; you'll still be invoking undefined behaviour.
1. For a more in-depth discussion, see Item 39 ("Make predicates pure functions") of Scott Meyers' Effective STL.
回答2:
Do we need to make sure a possible copy of predicate functor will not break our logic?
Yes, you should assume the predicates are copied. In C++11, you could consider using std::ref or std::cref.
An alternative would be to modify your comp
structure to take a set
by reference:
struct comp {
std::set<int>& s;
comp(std::set<int> s) : s(s) {}
bool operator()(int i)
{
return !(s.insert(i)).second;
}
};
Note: I am not making any statement on whether this would work with remove_if
, I am simply addressing the issue of copied predicates containing state that should not be copied.
Edit As pointed out in comments, the approach is fundamentally broken. The result of the predicate call should not depend on mutable state.
回答3:
Yes, they are allowed to copy the arguments an indeterminate number of times. A better approach than making the member set static would be to create the set outside of the functor and pass it as a constructor parameter. Internally store a pointer.
来源:https://stackoverflow.com/questions/11071553/are-standard-library-algorithms-allowed-to-copy-predicate-arguments