Иногда в классе функтора бывает разумно определить несколько форм вызова, тем самым отказавшись от адаптируемости (примеры таких ситуаций приведены в советах 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. Если объект не проходит
// проверку, он помечается как "плохой"
Допустим, у нас имеется контейнер объектов Widget
:
vector vw; // vw содержит объекты Widget
Для проверки всех объектов Widget
в контейнере vw
можно воспользоваться алгоритмом for_each
:
for_each(vw.begin(), vw.end(), test); // Вариант 1(нормально компилируется)
Но представьте, что test
является функцией класса Widget
, а не внешней функцией (то есть класс Widget
сам обеспечивает проверку своих объектов):
class Widget {
public:
…
void test(); // Выполнить самопроверку. Если проверка
… // завершается неудачей, объект помечается
}; // как "плохой"
В идеальном мире мы могли бы воспользоваться for_each
для вызова функции Widget::test
всех объектов вектора vw
:
for_each(vw.begin(), vw.end(),
&Widget::test); // Вариант 2(не компилируется!)
Более того, если бы наш мир был действительно идеальным, алгоритм for_each
мог бы использоваться и для вызова Widget::test
в контейнере указателей Widget*
:
list lpw; // Список lpw содержит указатели
// на объекты Widget
for_each(lpw.begin(), lpw.end(), // Вариант 3(не компилируется!)
&widget::test);
Но подумайте, что должно было бы происходить в этом идеальном мире. Внутри функции for_each
в варианте 1 вызывается внешняя функция, поэтому должен использоваться синтаксис 1. Внутри вызова for_each
в варианте 2 следовало бы использовать синтаксис 2, поскольку вызывается функция класса. А внутри функции for_each
в варианте 3 пришлось бы использовать синтаксис 3, поскольку речь идет о функции класса и указателе на объект. Таким образом, нам понадобились бы три разных версии for_each
— разве такой мир можно назвать идеальным?
В реальном мире существует только одна версия for_each
. Нетрудно представить себе возможную ее реализацию:
template
Function for_each(InputIterator begin, InputIterator end, Function f) {
while (begin != end) f(*begin++ );
}
Жирный шрифт используется для выделения того, что при вызове for_each
используется синтаксис 1. В STL существует всеобщее правило, согласно которому функции и объекты функций всегда вызываются в первой синтаксической форме (как внешние функции). Становится понятно, почему вариант 1 компилируется, а варианты 2 и 3 не компилируются — алгоритмы STL (в том числе и for_each
) жестко закодированы на использование синтаксиса внешних функций, с которым совместим только вариант 1.
Читать дальше