В этом случае типы, передаваемые binary_function
, совпадают с типами, передаваемыми operator
. Общее правило для классов функторов, получающих или возвращающих указатели, заключается в том, что unary_function
или binary_function
передаются в точности те типы, которые получает или возвращает operator
.
Помните, что базовые классы unary_function
и binary_function
выполняют только одну важную функцию — они предоставляют определения типов, необходимые для работы адаптеров, поэтому наследование от этих классов порождает адаптируемые объекты функций. Это позволяет использовать в программах следующие конструкции:
list
…
list
find_if(widgets.rbegin, widgets.rend, // Widget, не соответствующий
not1(MeetsThreshold
//(что бы это ни означало)
Widget w(
list
find_if(widgets.begin, widgets.end, // сортировки, определенном
bind2nd(WidgetNameCompare, w)); // WidgetNameCompare
Если бы классы функторов не определялись производными от unary_function
или binary_function
, ни один из этих примеров не компилировался бы, поскольку not1
и bind2nd
работают только с адаптируемыми объектами функций.
Объекты функций STL построены по образцу функций C++, а функции C++ характеризуются единственным набором типов параметров и одним типом возвращаемого значения. В результате STL неявно подразумевает, что каждый класс функтора содержит единственную функцию operator
, типы параметров и возвращаемого значения которой должны передаваться unary_function
или binary_function
(с учетом правил передачи ссылок и указателей, о которых говорилось ранее). Из этого следует одно важное обстоятельство: не поддавайтесь соблазну и не пытайтесь объединять функциональность WidgetnNameCompare
и PtrWidgetCompare
в одной структуре с двумя функциями operator
. В этом случае функтор будет адаптируемым по отношению лишь к одной из двух форм вызова (той, что использовалась при передаче параметров binary_function
), а пользы от такого решения будет немного — наполовину адаптируемый функтор ничуть не лучше неадаптируемого.
Иногда в классе функтора бывает разумно определить несколько форм вызова, тем самым отказавшись от адаптируемости (примеры таких ситуаций приведены в советах 7, 20, 23 и 25), но это скорее исключение, а не правило. Адаптируемость важна, и о ней следует помнить при разработке классов функторов.
Совет 41. Разберитесь, для чего нужны ptr_fun, mem_fun и mem_fun_ref
Загадочные функции ptr_fun/mem_fun/mem_fun_ref
часто вызывают недоумение. В одних случаях их присутствие обязательно, в других они не нужны… но что же они все-таки делают? На первый взгляд кажется, что они бессмысленно загромождают имена функций. Их неудобно вводить и читать, они затрудняют понимание программы. Что это — очередные пережитки прошлого STL (другие примеры приводились в советах 10 и 18) или синтаксическая шутка, придуманная членами Комитета по стандартизации с извращенным чувством юмора?
Действительно, имена выглядят довольно странно, но функции ptr_fun
, mem_fun
и mem_fun_ref
выполняют важные задачи. Если уж речь зашла о синтаксических странностях, надо сказать, что одна из важнейших задач этих функций связана с преодолением синтаксической непоследовательности C++.
В C++ существуют три варианта синтаксиса вызова функции f
для объекта x
:
f(x); // Синтаксис 1: f не является функцией класса
//(вызов внешней функции)
x.f; // Синтаксис 2: f является функцией класса, а х
// является объектом или ссылкой на объект
p->f; // Синтаксис 3: f является функцией класса,
// а р содержит указатель на х
Рассмотрим гипотетическую функцию, предназначенную для «проверки» объектов Widget
:
void test(Widget& w); // Проверить объект w. Если объект не проходит