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