Предположим, вы нарушили это ограничение. Ниже приведен плохо спроектированный класс предиката, который независимо от переданных аргументов возвращает true только один раз — при третьем вызове. Во всех остальных случаях возвращается false.
class BadPredicate: // Базовый класс описан
public unary_function
public:
BadPredicate: timesCalles(0) {} // Переменная timesCalled
// инициализируется нулем
bool operator(const Widget&) {
return ++timesCalled = 3;
}
private:
size_t timesCalled;
};
Предположим, класс BadPedicate используется для исключения третьего объекта Widget из контейнера vector:
vector
… // объектами Widget
vww.erase(remove_if(vw.begin, // Удалить третий объект Widget.
vw.end, // связь между erase и remove_if
BadPredcate), // описана в совете 32
vw.end);
Программа выглядит вполне разумно, однако во многих реализациях STL из вектора vw удаляется не только третий, но и шестой элемент!
Чтобы понять, почему это происходит, необходимо рассмотреть один из распространенных вариантов реализации remove_if. Помните, что эта реализация
template
FwdIterator remove_if(FwdIterator begin, FwdIterator end, Predicate p) {
begin = find_if(begin, end, p);
if (begin==end) return begin;
else {
FwdIterator next=begin;
return remove_copy_if(++next, end, begin, p);
}
}
Подробности нас сейчас не интересуют. Обратите внимание: предикат p сначала передается find_if, а затем remove_copy_if. Конечно, в обоих случаях p передается по значению — то есть
Первый вызов 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
public:
bool operator(const Widget&) const {
return ++timesCalled == 3; // Ошибка! Изменение локальных данных
} // в константной функции невозможно
};
Из-за простоты этого решения я чуть было не озаглавил этот совет «Объявляйте operator константным в предикатных классах», но этой формулировки недостаточно. Даже константные функции могут обращаться к mutablе-переменным, неконстантным локальным статическим объектам, неконстантным статическим объектам класса, неконстантным объектам в области видимости пространства имен и неконстантным глобальным объектам. Хорошо спроектированный предикатный класс должен обеспечить независимость функций operator и от этих объектов. Объявление константных функций operator в предикатных классах operator является константной, но это еще не все. Она должна быть «чистой» функцией.