Функторы

Пятница, 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”) ;

Как видите, с таким же успехом можно воспользоваться именем любой функции класса. Единственная причина для использования оператора () — в том, что он предельно ясно выражает ваши намерения. Если класс существует лишь для того, чтобы обслуживать обратные вызовы подобного рода, пользуйтесь оператором () ; в противном случае пользуйтесь обычными функциями класса.

Сохранить в:

  • Twitter
  • Grabr
  • WebDigg
  • email
  • Facebook
  • FriendFeed
  • Google Bookmarks
  • Yandex
  • Memori
  • MisterWong
  • BobrDobr
  • Moemesto
  • News2
  • 100zakladok
  • Baay!

Случайные записи

Комментарии и пинг сейчас закрыты.