|
Функторы Напоследок мы познакомимся с одной диковинкой 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"); } Как видите, с таким же успехом можно воспользоваться именем любой функции класса. Единственная причина для использования оператора () - в том, что он предельно ясно выражает ваши намерения. Если класс существует лишь для того, чтобы обслуживать обратные вызовы подобного рода, пользуйтесь оператором (); в противном случае пользуйтесь обычными функциями класса. |
Copyright 2005. Климов Александр. All Right Reserved.