С позиций STL прежде всего необходимо помнить о том, что классы функторов, использующие данную методику, должны поддерживать соответствующий механизм копирования. Если бы вы были автором приведенного выше класса
BPFC
, то вам пришлось бы позаботиться о том, чтобы копирующий конструктор выполнял осмысленные действия с объектом
BPFCImpl
, на который он ссылается. Возможно, простейшее решение заключается в организации подсчета ссылок при помощи указателя
shared_ptr
из библиотеки
Boost
или его аналога (см. совет 50).
В сущности, копирующий конструктор
BPFC
— единственное, о чем вам придется побеспокоиться в контексте данного примера, поскольку при передаче и получении функторов от функций STL всегда происходит копирование (помните, что говорилось выше о передаче по значению?). Из этого вытекают два требования: компактность и мономорфизм.
Совет 39. Реализуйте предикаты в виде «чистых» функций
Для начала разберемся с основными терминами.
• Предикатом называется функция, возвращающая тип
bool
(или другое значение, которое может быть автоматически преобразовано к
bool
). Предикаты широко используются в STL. В частности, функции сравнения в стандартных ассоциативных контейнерах представляют собой предикаты. Предикатные функции часто передаются в виде параметров таким алгоритмам, как
find_if
, и различным алгоритмам сортировки (обзор алгоритмов сортировки приведен в совете 31).
• «Чистой» функцией называется функция, возвращаемое значение которой зависит только от параметров. Если
f
— «чистая» функция, а
x
и
y
— объекты, то возвращаемое значение
f(x, y)
может измениться только в случае изменения х или у.
В C++ все данные, используемые «чистыми» функциями, либо передаются в виде параметров, либо остаются постоянными на протяжении всего жизненного цикла функции (естественно, такие постоянные данные объявляются с ключевым словом const). Если бы данные, используемые «чистой» функцией, могли изменяться между вызовами, то вызов этой функции в разные моменты времени с одинаковыми параметрами мог бы давать разные результаты, что противоречит определению «чистой» функции.
Из сказанного должно быть понятно, что нужно сделать, чтобы предикаты были «чистыми» функциями. Мне остается лишь убедить читателя в том, что эта рекомендация обоснована. Для этого придется ввести еще один термин.
• Предикатным классом называется класс функтора, у которого функция
operator
является предикатом, то есть возвращает
true
или
false
. Как и следует ожидать, во всех случаях, когда STL ожидает получить предикат, может передаваться либо настоящий предикат, либо объект предикатного класса.
Обещаю, что новых терминов больше не будет. Теперь давайте разберемся,
почему следует выполнять рекомендацию данного совета.
В совете 38 объяснялось, что объекты функций передаются по значению, поэтому при проектировании необходимо позаботиться о возможном копировании. Для объектов функций, являющихся предикатами, существует и другой аргумент в пользу специальной поддержки копирования. Алгоритмы могут создавать копии функторов и хранить их определенное время перед применением, причем некоторые реализации алгоритмов этим активно пользуются. Важнейшим следствием этого факта является то, что предикатные функции должны быть «чистыми».
Предположим, вы нарушили это ограничение. Ниже приведен плохо спроектированный класс предиката, который независимо от переданных аргументов возвращает
true
только один раз — при третьем вызове. Во всех остальных случаях возвращается
false
.
class BadPredicate: // Базовый класс описан
public unary_function<Widget, bool>{ // в совете 40