Материалы книги получены с http://www.itlibitum.ru/
Функторы
Напоследок мы познакомимся с одной диковинкой C++, которая называется функтором (functor).
Функторы играют для функций ту же роль, что и интерфейсные указатели для объектов. Одна из
проблем, вечно мучивших программистов на С - то, что все функции находятся в глобальном
пространстве имен, то есть вызванная функция имеет доступ только к данным, хранящимся в ее
аргументах, и глобальным переменным. Если передать адрес функции еще кому-то, то при вызове
функции по адресу она не будет помнить, как выглядел окружающий мир во время получения ее
адреса.
В таких языках, как Паскаль, эта проблема изящно решается получением замыкания (closure) на
момент получения адреса функции.
procedure p(n: integer);
var
procedure fn;
begin
do_something(n);
end;
begin
callback(@fn);
end;
В качестве аргумента процедура саllbackfn получает адрес другой процедуры. В данном примере ей передается адрес fn. При вызове fn из callbackfn первая имеет доступ к переменным,
находившимся в стеке в момент получения адреса. В нашем примере fn знает значение переменной n на момент вызова саllbackfn.
Замыкания чрезвычайно полезны для обработки обратных вызовов (callback), поскольку функция
обратного вызова кое-что знает о том, почему она была вызвана. В С вложенных функций не
существует, а следовательно, замыкания невозможны - их место занимают функторы.
class Fn {
private:
int number;
public:
f(int n) : number(n) {}
void operator() () { do_something(number); }
};
void callbackfn(Fn);
void p(int n)
{
callbackfn(Fn(n));
}
void callbackfn(Fn fn)
{
// Что-то делаем
fn(); // Вызвать «функцию» fn с помощью функции operator()
}
Весь секрет кроется в двух выражениях. Функция callbackfn(Fn(n)) передает функции анонимный
экземпляр класса Fn. Аргумент его конструктора содержит информацию, включаемую в
«псевдозамыкание», которое поддерживается переменными класса Fn. Выражение fn(); может
показаться обычным вызовом функции, но на самом деле в нем вызывается операторная функция
operator() класса Fn. В свою очередь, эта функция вызывает глобальную функцию do_something с
использованием данных замыкания. И кому после этого нужен Паскаль?
Операторная функция operator() может вызываться с произвольным набором аргументов. Чтобы
добавить новые аргументы, укажите их во вторых скобках в объявлении класса. Также разрешается многократная перегрузка оператора () с разными сигнатурами. Ниже приведен тот же пример, в котором одна из версий операторной функции operator() вызывается с аргументом.
class Fn {
private:
int number;
public:
f(int n) : number(n) {}
void operator() () { do_something(number); }
void operator() (char* s)
{
do_something(number);
cout << "Что-то делаю с " << s << endl;
}
};
void callbackfn(Fn);
void p(int n)
{
callbackfn(Fn(n));
}
void callbackfn(Fn fn)
{
// Что-то делаем
fn("callbackfn");
}
Эта маленькая идиома выглядит довольно изящно, однако того же эффекта можно добиться и без
оператора ().
class Fn {
private:
int number;
public:
f(int n) : number(n) {}
void do_something() () { ::do_something(number); }
void do_something() (char* s)
{
do_something(number);
cout << "Что-то делаю с " << s << endl;
}
};
void callbackfn(Fn);
void p(int n)
{
callbackfn(Fn(n));
}
void callbackfn(Fn fn)
{
// Что-то делаем
fn.do_something("callbackfn");
}
Как видите, с таким же успехом можно воспользоваться именем любой функции класса. Единственная причина для использования оператора () - в том, что он предельно ясно выражает ваши намерения. Если класс существует лишь для того, чтобы обслуживать обратные вызовы подобного рода, пользуйтесь оператором (); в противном случае пользуйтесь обычными функциями класса.
Назад Содержание Далее
|