Анимация
JavaScript
|
Главная Библионтека template <clas ParentFunctor, typename Fun> class FunctorHandler : public Functorlmpl < typename ParentFunctor::ResultType, typename ParentFunctor::ParmList > publi c: typedef typename ParentFunctor::ResultType ResultType; FunctorHandlerCconst Fun& fun) : fun (fun) {} FunctorHandler* CloneO const { return new FunctorHandler(*this); } ResultType operatorOO { return fun 0 ; ResultType operatorO(typename ParentFunctor::Parml pl) { return fun Cpl); ResultType operatorO (typenime ParentFunctor:: Parml pl, typename ParentFunctor::Parm2 p2) return fun Cpl,p2); private: Fun fun ; Класс FunctorHandler почти не отличается от класса Functor. Он переадресовывает запросы сохраняемой переменной-члену. Основное отличие заключается в том, что функтор хранится как значение, а не как указатель. Вот почему функторы являются неполиморфными типами с обычной семантикой копирования. Обратите внимание на внутренние типы ParentFunctor:: Resul tType, ParentFunctor: :Parml и ParentFunctor: :Parm2. В классе FunctorHandler реализован простой конструктор, функция клонирования и несколько версий оператора ()• Компилятор сам выберет правильный вариант. Если перегрузка оператора () выполнена неверно, компилятор выдаст сообщение об ощибке. В реализации класса FunctorHadlег нет ничего необычного, но это вовсе не означает, что она тривиальна. В следующем разделе будет показано, какой универсальностью обладает этот небольшой шаблонный класс. С помощью объявления класса FunctorHandler легко написать шаблонный конструктор класса Functor. template <typename R, class TList> template <typename Fun> Functor<R, TList>::FunctorCconst Fun& fun) : splmpl Cnew FunctorHandler<Functor, Fun>(fun)); Здесь нет никакой ошибки. Два шаблонных параметра указаны правильно: выражение template <class R, class TList> относится к классу Functor, а template <typename Fun> является параметром конструктора. В стандарте языка такой код называется "шаблонным определением члена вне класса" ("out-of-class member template definition"). В теле конструктора переменная splmpl устанавливается на новый объект типа FunctorHandl ег, конкретизированный и проинициализированный соответствующими аргументами. Есть еще кое-что, о чем следовало бы упомянуть, кое-что, позволяющее лучше понять реализацию класса FunctorHandlег. Обратите внимание на то, что при входе в тело конструктора класса Functor полная информация о типах уже содержится в шаблонном параметре Fun. При выходе из конструктора эта информация теряется, поскольку всем объектам класса Functor известен лишь указатель splmpl , ссылающийся на базовый класс Functorlmpl. Эта очевидная потеря информации весьма примечательна: конструктор знает тип и действует подобно фабрике, преобразующей этот тип в полиморфное поведение. Информация о типах сохраняется в динамическом указателе на класс Functorlmpl. Хотя мы написали лишь часть кода (просто несколько функций, состоящих из одной строки), уже можно кое-что проверить. предположим, что файл Functor.h включен в реализацию класса Functor #include "Functor.h" #include <iostream> для небольших программ на С++ можно применять директиву using using namespace std; Определяем тестируемый функтор struct TestFunctor void operator()(int i, double d) { cout « "TestFunctor::operator()(" « i « ", " « d « ") called. \n"; int mainO { TestFunctor f; Functor<void, TYPELlST 2(int, double)> cmd(f); cmd(4, 4.5); Эта маленькая программа выводит на печать следующую строку. TestFunctor::operator()(4, 4.5) called. Следовательно, мы достигли своей цели. Теперь можно идти дальше. 5.7. Один пишем, два в уме Читая предыдущий раздел, не задавались ли вы вопросом: "Почему мы не начали с реализации простых указателей на обычные функции?". Зачем мы перескочили сразу к функторам, шаблонам и т.п.? Ответ прост - в настоящее время поддержка указателей на обычные функции уже реализована. Изменим немного нашу тестовую профамму. #include "Functor.h" #include <iostream> using namespace std; Определяем тестируемую функцию void TestFunction (int i, double d) { cout « "TestFunction(" « i « ", " « d « ") called. \n"; int mainO { Functor<void, TYPELIST 2(int, double)> cmd(TestFunction); выведет на печать строку "TestFunction(4, 4.5) called." cmd(4, 4.5); Объяснение этого приятного сюрприза кроется в способе вьгеода шаблонных параметров. Когда компилятор обнаруживает класс Functor, созданный из функции TestFunction, он вынужден проверить шаблонный консфуктор. Затем компилятор конкретизирует шаблонный консфуктор шаблонным аргументом void (&)(int, double), представляющим собой тип значения, возвращаемого функцией TestFunction. Консфуктор конкретизирует класс FunctorHandler<Functor<.. .>, void (&)(int, double)>. Следовательно, переменная fun в классе FunctorHandler также имеет тип void (&)(int, double). При вызове оператора FunctorHandler<.. .>: :operator() он передается функции fun (). Это вполне допустимая синтаксическая консфукция, означающая вызов функции с помощью указателя. Итак, класс FunctorHandler поддерживает указатели на внешние функции по двум причинам: благодаря синтаксической схожести указателей на функхши и функторов и особенностям механизма вывода типов в языке С++. Однако есть одна проблема. (У всех есть свои недостатки, не так ли?) При пере-фузке функции TestFunction (или любой другой функции, которая передается классу Functor<.. .>) возникает неопределенность. Ее причина заключается в том, что при перефузке функции TestFunction тип символа TestFunction становится неопределенным. Чтобы проиллюстрировать этот факт, поместим перефуженную версию функции TestFunction прямо перед функцией main. Объявление перегруженной функции TestFuction (определение не обязательно) void TestFunction(int); Неожиданно компилятор сообщает, что не может распознать, какую из перефуженных версий функции TestFunction следует использовать. Поскольку есть две функции с именем TestFunction, одного имени для идентификации функции недостаточно. В сущности, при перефузке есть два способа идентифицировать конкретную функцию: с помощью инициализации (или присваивания) или с помощью приведения типов. Проиллюстрируем оба метода. как и выше, функция TestFunction перегружена int mainO для удобства используется оператор typedef Глава 5. Обобщенные функторы 135 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 [ 39 ] 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |