Анимация
JavaScript
|
Главная Библионтека оператор вызова функции имеет точный смысл - "выполнить". Однако намного важнее то, что этот оператор обеспечивает синтаксическое единообразие. Класс Functor не только передает вызов вызываемой сущности, он и сам является таковой. В подобном случае объект класса Functor может содержать другие объекты этого класса. С этого момента мы будем считать класс Functor частью множества вызываемых сущностей. Это позволит нам многие вещи делать единообразно. Проблемы возникнут, как только мы попытаемся определить оболочку класса Functor. Вначале она может выглядеть следующим образом. class Functor { public: void operatorO О; другие функции-члены private: реализация класса Первый вопрос: какой тип должно иметь значение, возвращаемое оператором ()? Должно ли оно иметь тип void? В некоторых случаях может понадобиться вернуть что-то еще, например, значение типа bool или std::string. Нет никаких причин отказываться от возврата параметризованных значений. Для рещения таких проблем предназначены шаблонные классы, позволяющие избежать лишних хлопот. template <typename ResultType> class Functor public: ResultType operator()(); другие функции-члены private: Реализация Это решение выглядит вполне приемлемо, хотя теперь у нас уже не один класс Functor, а целое семейство. Это вполне разумно, поскольку функторы, возвращающие строки, и функторы, возвращающие целые числа, имеют разные функциональные возможности. Второй вопрос: должен ли оператор (), принадлежащий функтору, также получать аргументы? Может возникнуть необходимость передать объекту класса Functor некую информацию, которая была недоступна в момент его создания. Например, если щелчки мыщью в окне пересылаются через функтор, вызывающий объект передает информацию о координатах окна (известную только в момент вызова) объекту класса Functor, вызывая его оператор (). Более того, в обобщенном профаммировании количество парамефов не офаничивает-ся, причем они могут иметь любой тип. Таким образом, нет никаких причин накладывать Офаничения ни на количество, ни на тип передаваемых функтору параметров. Отсюда следует, что каждый функтор определяется типом возвращаемого им значения и типами его аргументов. Для этого понадобится мощная языковая поддержка: переменные шаблонные парамефы в сочетании с переменными параметрами вызова функций. К сожалению, переменных шаблонных парамефов в языке С++ просто нет. Вместо них предусмотрены функции с переменными аргументами (так же, как и в язьпсе С). В языке С с их помощью выполняется значительная часть работы (при условии, что вы очень осторожны), но для языка С++ этого недостаточно. Переменные аргументы поддерживаются с помощью эллипсисов (таких как printf или scanf). Вызов фунгащй printf или scanf, когда спецификация формата не соответствует количеству и типу их аргументов, - весьма распространенная и опасная ощибка, иллюстрирующая недостатки эллипсисов. Механизм переменных параметров ненадежен, относится к низкому уровню и не соответствует объектной модели языка С++. Короче говоря, используя эллипсисы, вы остаетесь без типовой безопасности, объектной семантики (применение объектов, содержащих эллипсисы, приводит к непредсказуемым последствиям) и ссылочных типов. Для вызываемых функций недоступно даже количество их аргументов. Действительно, там, где есть эллипсисы, языку С++ делать нечего. В качестве альтернативы можно офаничить количество аргументов функтора произвольным достаточно большим числом. Такая неопределенность - одна из самых неприятнь[х проблем для профаммиста. Однако этот выбор можно сделать на основе экспериментальных наблюдений. В библиотеках (особенно старых) количество парамефов функций не превыщает 12. Установим предельное количество парамефов равным 15 и забудем об этой неприятности. Даже это не облегчило нашей участи. В языке С++ не допускается применение шаблонов, имеющих одинаковые имена, но разное количество параметров. Это значит, что приведенный ниже фрагмент профаммы является неправильным. функтор без аргументов template <typename ResultType> class Functor { };" функтор с одним аргументом template <typename ResultType, typename Parml> class Functor };" Называть шаблонные классы именами Functorl, Functor2 и так далее может оказаться зафуднительно. В главе 3 описаны списки типов, позволяющие работать с коллекциями типов. Типы парамефов функтора также образуют коллекцию, поэтому списки типов подойдут нам идеально. Определение шаблонного класса Functor с помощью списков типов приведено ниже. Функтор, имеющий любое количество аргументов любого типа template <typename ResultName, class TList> class Functor { }" Возможная конкретизация этого класса выглядит следующим образом. Определяем функтор, получающий параметры типа int и double и возвращающий значение типа double Functor<double, TYPELlST 2(int, double)> myFunctor; Одним из достоинств этого подхода является возможность повторно использовать сущности, определенные списками типов, а не разрабатывать новые. Как мы вскоре убедимся, списки типов хотя и полезны, но не решают всех проблем. Мы по-прежнему вынуждены создавать отдельную реализацию функтора для каждого конкретного количества аргументов. В дальнейшем для простоты офаничим-ся двумя аргументами. Масштабировать профамму для конкретного количества параметров (не больше 15) можно с помошью включения заголовочного файла Functor.h. Полиморфный класс Functorlml, пофуженный в класс Functor, имеет те же самые шаблонные параметры. template <typename R, class TList> class Functorlmpl; Класс Functorlmpl определяет полиморфный интерфейс, абстрагирующий вызов функции. Для каждого конкретного количества параметров определена явная специализация класса Functorlmpl (глава 2). В каждой специализации определена чисто виртуальная функция () для определенного количества и типов параметров. template <typename R> class Functorlmpl<R, NullType> public: virtual R operatorO О = 0; virtual Functorlmpl* cloneQ const = 0; virtual -Functorlmpl() {} template <typename R, typename Pl> class Functorimpl<R, TYPELIST 1(P1)> { public: virtual R operatorO(Pl) = 0; virtual Functorlmpl* cloneO const = 0; virtual -FunctorlmplО {} template <typename R, typename Pl, typename P2> class Functorlmpl<R, TYPELIST 2(P1, P2)> public: virtual R operatorO (Pl, P2) = 0; virtual Functorlmpl* clone() const = 0; virtual -FunctorlmplО {} Классы Functorlmpl представляют собой частичные специализации исходного шаблонного класса Functorlmpl. Их свойства подробно описаны в главе 2. В нашем случае частичная шаблонная специализация позволяет определять разные версии класса Functorlmpl в зависимости от количества элементов в списке типов. Кроме оператора О в классе Functorlmpl определены две вспомогательные функции-члены- clone и виртуальный деструктор. Функция Clone предназначена для создания полиморфной копии объекта класса Functorlmpl. (Детали полиморфного Использование ключевых слов typename или class для определения шаблонных параметров приводит к эквивалентным результатам. В книге по умолчанию при определении шаблонных параметров, которые могут относиться к элементарным типам (например i nt), принято использовать ключевое слово typename, а ключевое слово class применяется для шаблонных параметров, тип которых определяется пользователем. 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 |