Ранее в этом совете уже упоминалось о том, что всюду, где STL ожидает получить предикатную функцию, может передаваться либо реальная функция, либо объект предикатного класса. Этот принцип действует в обоих направлениях. В любом месте, где STL рассчитывает получить объект предикатного класса, подойдет и предикатная функция (возможно, модифицированная при помощи ptr_fun — см. совет 41). Теперь вы знаете, что функции operator в предикатных классах должны быть «чистыми» функциями, поэтому ограничение распространяется и на предикатные функции. Следующая функция также плоха в качестве предиката, как и объекты, созданные на основе класса BadPredcate:
bool anotherBadPredicate(const Widget&, const Widget&) {
static int timesCalled = 0; // Нет! Нет! Нет! Нет! Нет! Нет!
return ++timesCalled == 3; // Предикаты должны быть "чистыми"
} // функциями, а "чистые" функции
// не имеют состояния
Как бы вы ни программировали предикаты, они всегда должны быть «чистыми» функциями.
Совет 40. Классы функторов должны быть адаптируемыми
Предположим, у нас имеется список указателей Widget* и функция, которая по указателю определяет, является ли объект Widget «интересным»:
list
bool isInteresting(const Widget *pw);
Если потребуется найти в списке первый указатель на «интересный» объект Widget, это делается легко:
list
isIntersting);
if (i != widgetPts.end) {
… // Обработка первого "интересного"
} // указателя на Widget
С другой стороны, если потребуется найти первый указатель на «неинтересный» объект Widget, следующее очевидное решение не компилируется:
list
not1(isInteresting)); // Ошибка! He компилируется
Перед not1 к функции isInteresting необходимо применить ptr_fun:
list
find_if(widgetPtrs.begin, widgetPtrs.end,
not1(ptr_fun(isInteresting))); // Нормально
if (i != widgetPtrs.end) { // Обработка первого
… // "неинтересного" указателя
} //на Widget
При виде этого решения невольно возникают вопросы. ptr_fun к isInteresting перед not1? Что ptr_fun для нас делает и почему начинает работать приведенная выше конструкция?
Ответ оказывается весьма неожиданным. Вся работа ptr_fun сводится к предоставлению нескольких определений типов. Эти определения типов необходимы для not1, поэтому применение not1 к ptr_fun работает, а непосредственное применение not1 к isInteresting не работает. Примитивный указатель на функцию isInteresting не поддерживает определения типов, необходимые для not1.
Впрочем, not1 — не единственный компонент STL, предъявляющий подобные требования. Все четыре стандартных адаптера (not1, not2, bind1st и bind2nd), а также все нестандартные STL-совместимые адаптеры из внешних источников (например, входящие в SGI и Boost — см. совет 50), требуют существования некоторых определений типов. Объекты функций, предоставляющие необходимые определения типов, называются