Функторы
Пятница, 13 Авг 2010 8:49Напоследок мы познакомимся с одной диковинкой C++, которая называется функтором (functor). Функторы играют для функций ту же роль, что и интерфейсные указатели для объектов. Одна из проблем, вечно мучивших программистов на С — то, что все функции находятся в глобальном пространстве имен, то есть вызванная функция имеет доступ только к данным, хранящимся в ее аргументах, и глобальным переменным. Если передать адрес функции еще кому-то, то при вызове функции по адресу она не будет помнить, как выглядел окружающий мир во время получения ее адреса.
В таких языках, как Паскаль, эта проблема изящно решается получением замыкания (closure) на момент получения адреса функции.
procedure p(n: integer); var
procedure fn;
begin
do_something(n);
end; begin
callback(@fn); end;
В качестве аргумента процедура cal 1 backf n получает адрес другой процедуры. В данном примере ей передается адрес fn. При вызове fn из call backf n первая имеет доступ к переменным, находившимся в стеке в момент получения адреса. В нашем примере f n знает значение переменной n на момент вызова cal I backf n.
Замыкания чрезвычайно полезны для обработки обратных вызовов (callback), поскольку функция обратного вызова кое-что знает о том, почему она была вызвана. В С вложенных функций не существует, а следовательно, замыкания невозможны — их место занимают функторы.
class Fn { private:
int number; public:
f(int n) : number (n) {}
void operatorQ () { do_something (number) ; }
void callbackfn(Fn) ;
void p(int n) {
cal 1 backf n ( Fn (n) ) ;
}
void call backf n(Fn fn)
{
// что-то делаем
fn(); // вызвать «функцию» fn с помощью функции operatorQ
Весь секрет кроется в двух выражениях. Функция call backf n(Fn(n)) передает функции анонимный экземпляр класса Fn. Аргумент его конструктора содержит информацию, включаемую в «псевдозамыкание», которое поддерживается переменными класса Fn. Выражение fn(); может показаться обычным вызовом функции, но на самом деле в нем вызывается операторная функция operate г () класса Fn. В свою очередь, эта функция вызывает глобальную функцию do_something с использованием данных замыкания. И кому после этого нужен Паскаль?
Операторная функция operate r () может вызываться с произвольным набором аргументов. Чтобы добавить новые аргументы, укажите их во вторых скобках в объявлении класса. Также разрешается многократная перегрузка оператора () с разными сигнатурами. Ниже приведен тот же пример, в котором одна из версий операторной функции operate r () вызывается с аргументом.
class Fn { private:
int number; public:
f(int n) : number (n) {}
void operatorQ () { do_something(number) ; }
void operatorQ (char* s)
{
do_something(number) ;
cout « «что-то делаю с « « s « endl ;
void cal 1 backf n(Fn) ;
void p(int n)
{
cal 1 backf n ( Fn (n) ) ;
void callbackfn(Fn fn) {
// что-то делаем
fn(«callbackfn»);
}
Эта маленькая идиома выглядит довольно изящно, однако того же эффекта можно добиться и без оператора () .
class Fn { private:
int number; public:
f(int n) : number (n) {}
void do_somethingO () { : :do_something(number) ; }
void do_somethingO (char* s)
{
do_something (number) ;
cout « «что-то делаю с « « s « endl ;
void callbackfn(Fn) ;
void p(int n) {
callbackfn(Fn(n)); }
void callbackfn(Fn fn) {
// что-то делаем
f n . do_somethi ng(“cal 1 backf n”) ;
Как видите, с таким же успехом можно воспользоваться именем любой функции класса. Единственная причина для использования оператора () — в том, что он предельно ясно выражает ваши намерения. Если класс существует лишь для того, чтобы обслуживать обратные вызовы подобного рода, пользуйтесь оператором () ; в противном случае пользуйтесь обычными функциями класса.