Шрифт:
Более того, если бы наш мир был действительно идеальным, алгоритм
for_each
мог бы использоваться и для вызова Widget::test
в контейнере указателей Widget*
: list<Widget*> 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<typename InputIterator, typename Function>
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. Теперь понятно, для чего нужны функции
mem_fun
и mem_fun_ref
. Они обеспечивают возможность вызова функций классов (обычно вызываемых в синтаксисе 2 и 3) при помощи синтаксиса 1. Принцип работы
mem_fun
и mem_fun_ref
прост, хотя для пущей ясности желательно рассмотреть объявление одной из этих функций. В действительности они представляют собой шаблоны функций, причем существует несколько вариантов mem_fun
и mem_fun_ref
для разного количества параметров и наличия-отсутствия константности адаптируемых ими функций классов. Одного объявления вполне достаточно, чтобы разобраться в происходящем: template<typename R, typename C> // Объявление mem_fun для неконстантных
mem_fun_t<R, C> // функций без параметров. С - класс.
mem_fun(R(C::*pmf)); // R - тип возвращаемого значения функции.
// на которую ссылается указатель
Функция
mem_fun
создает указатель pmf
на функцию класса и возвращает объект типа mem_fun_t
. Тип представляет собой класс функтора, содержащий указатель на функцию и функцию operator
, которая по указателю вызывает функцию для объекта, переданного operator
. Например, в следующем фрагменте: list<Widget*> lpw; // См. ранее
…
for_each(lpw.begin, lpw.end,
mem_fun(&Widget::test)); // Теперь нормально компилируется
При вызове
for_each
передается объект типа mem_fun_t
, содержащий указатель на Widget::test
. Для каждого указателя Widget*
в lpw
алгоритм for_each
«вызывает» объект mem_fun_t
с использованием синтаксиса 1, а этот объект непосредственно вызывает Widget::test
для указателя Widget*
с использованием синтаксиса 3. В целом
mem_fun
приводит синтаксис 3, необходимый для Widget::test
при использовании с указателем Widget*
, к синтаксису 1, используемому алгоритмом for_each
. По вполне понятным причинам такие классы, как mem_fun_t
, называются адаптерами объектов функций. Наверное, вы уже догадались, что по аналогии со всем, о чем говорилось ранее, функции mem_fun_ref
адаптируют синтаксис 2 к синтаксису 1 и генерируют адаптеры типа mem_fun_ref_t
. Объекты, создаваемые функциями
mem_fun
и mem_fun_ref
, не ограничиваются простой унификацией синтаксиса для компонентов STL. Они (а также объекты, создаваемые функцией ptr_fun
) также предоставляют важные определения типов. Об этих определениях уже было рассказано в совете 40, поэтому я не стану повторяться. Тем не менее, стоит разобраться, почему конструкция for_each(vw.begin, vw.end, test); // См. ранее, вариант 1.
// Нормально компилируется
компилируется, а следующие конструкции не компилируются:
for_each(vw.begin, vw.end, &Widget::test); // См. ранее, вариант 2.
// Не компилируется.
for_each(lpw.begin, lpw.end, &Widget::test); // См. ранее, вариант 3.
// Не компилируется
При первом вызове (вариант 1) передается настоящая функция, поэтому адаптация синтаксиса вызова для
for_each
не нужна; алгоритм сам вызовет ее с правильным синтаксисом. Более того, for_each
не использует определения типов, добавляемые функцией ptr_fun
, поэтому при передаче test
функция ptr_fun
не нужна. С другой стороны, добавленные определения не повредят, поэтому следующий фрагмент функционально эквивалентен приведенному выше: