Подробности нас сейчас не интересуют. Обратите внимание: предикат
p
сначала передается
find_if
, а затем
remove_copy_if
. Конечно, в обоих случаях
p
передается по значению — то есть копируется (теоретически возможны исключения, но на практике дело обстоит именно так; за подробностями обращайтесь к совету 38).
Первый вызов
remove_if
(расположенный
в клиентском коде, удаляющем третий элемент из
vw
) создает анонимный объект
BadPredcate
с внутренней переменной
timesCalled
, равной 0. Этот объект, известный в
remove_if
под именем
p
, затем копируется в
find_if
, поэтому
find_if
тоже получает объект
BadPredicate
с переменной
timesCalled
, равной 0. Алгоритм
find_if
«вызывает» этот объект, пока тот не вернет
true
; таким образом, объект вызывается три раза. Затем
find_if
возвращает управление
remove_if
.
Remove_if
продолжает выполняться и в итоге вызывает
remove_copy_if
, передавая в качестве предиката очередную копию
p
. Но переменная
timesCalled
объекта
p
по-прежнему равна 0! Ведь алгоритм
find_if
вызывал не
p
, а лишь копию
p
. В результате при третьем вызове из
remove_copy_if
предикат тоже вернет
true
. Теперь понятно, почему
remove_if
удаляет два объекта
Widget
вместо одного.
Чтобы обойти эту лингвистическую ловушку, проще всего объявить функцию
operator
с ключевым словом
const
в предикатном классе. В этом случае компилятор не позволит изменить переменные класса:
class BadPredicate:
public unary_function<Widget, bool> {
public:
bool operator(const Widget&) const {
return ++timesCalled == 3; // Ошибка! Изменение локальных данных
} // в константной функции невозможно
};
Из-за простоты этого решения я чуть было не озаглавил этот совет «Объявляйте
operator
константным в предикатных классах», но этой формулировки недостаточно. Даже константные функции могут обращаться к
mutablе
– переменным, неконстантным локальным статическим объектам, неконстантным статическим объектам класса, неконстантным объектам в области видимости пространства имен и неконстантным глобальным объектам. Хорошо спроектированный предикатный класс должен обеспечить независимость функций
operator
и от этих объектов. Объявление константных функций
operator
в предикатных классах необходимо для правильного поведения, но не достаточно. Правильно написанная функция
operator
является константной, но это еще не все. Она должна быть «чистой» функцией.
Ранее в этом совете уже упоминалось о том, что всюду, где STL ожидает получить предикатную функцию, может передаваться либо реальная функция, либо объект предикатного класса. Этот принцип действует в обоих
направлениях. В любом месте, где STL рассчитывает получить объект предикатного класса, подойдет и предикатная функция (возможно, модифицированная при помощи
ptr_fun
— см. совет 41). Теперь вы знаете, что функции
operator
в предикатных классах должны быть «чистыми» функциями, поэтому ограничение распространяется и на предикатные функции. Следующая функция также плоха в качестве предиката, как и объекты, созданные на основе класса