|
Программирование >> Многопоточная библиотека с принципом минимализма
Связывание является мощным средством еще и по другой причине. Представьте, что вы применяете класс Functor для вычислений, а его аргументы - это окружение, необходимое для их выполнения. До сих пор класс Functor задерживал вычисления, сохраняя указатели на функции и указатели на методы. Однако в классе Functor хранятся лищь вычисления, но нет информации об их окружении. Связывание позволяет функтору хранить часть окружения вместе с вычислениями и сократить потребность в пересылке переменных окружения во время вызова. Перед тем как перейти к реализации, подытожим наши требования. В конкретизации класса Functor<R, TList> мы хотим связать первый аргумент (TLi st:: Head) с фиксированным значением. Следовательно, типом возвращаемого значения является класс Functor<R, TList::Tai1>. Реализовать класс BinderFi rst очень легко. Нужно лишь учесть, что здесь используются две конкретизации класса Functor: входная и результирующая. Входной тип Functor передается в качестве параметра ParentFunctor, а результирующий тип Functor вычисляется. template <class Incoming> class BinderFirst : public Functorlmpl<typename Incoming::ResultType, typename incomi ng::Arguments::Tai1> typedef Functor<typename Incoming::ResultType, incoming::Arguments::Tail> Outgoing; typedef typename incoming::parml Bound; typedef typename incoming::ResultType ResultType; public: BinderFirstCconst lncoming& fun, Bound bound) : fun (fun), bound (bound) BinderFirst* CloneO const { return new BinderFirst(*this); } ResultType operator()() { return fun (bound ); ResultType operatorC)(typename Outgoing::Parml pi) return fun (bound , pi); ResultType operatorO(typename Outgoing::Parml pi, typename Outgoing::Parm2 p2) return fun (bound , pi, p2); private: Incoming fun ; Bound bound ; Шаблонный класс BinderFirst работает в сочетании с шаблонной функцией BindFirst. Достоинством шаблонной функции BindFirst является то, что она автоматически выводит свои шаблонные параметры из типов получаемых ею фактических параметров. Определение класса BinderFirstTraits находится в файле Functor.h template <class Fctor> typename Private::BinderFirstTraits<Fctor>::BoundFunctorType BindFi rst( const Fctor& fun, typename Fetor::Parml bound) typedef typename private::BinderFirstTraits<Fctor>::BoundFunctorType Outgoing; return OutgoingCstd::auto ptr<typename Outgoing::lmpl>( new BinderFirst<Fctor>(fun, bound))); Связывание прекрасно сочетается с автоматическим преобразованием типов, наделяя класс Functor невероятной гибкостью. Это иллюстрирует следующий пример. const char* FunCint i, int j) { cout Fun( i , j ) called\n ; int mainC) { Functor<const char*, TYPELIST 2(char, int)> fl(Fun); Functor<std::string, TYPELlST l(double)> f2( BindFirstCfl, 10)); Выводит на печать строку Fun(10, 15) called f2a5); 5.11. Сцепление в книге Gamma et al. (1995) приведен пример класса MacroCommand, в котором хранится линейный набор (список или вектор) объектов класса Command. При выполнении этого класса последовательно выполняются все хранящиеся в нем команды. Это свойство может оказаться очень полезным. Например, вернемся к примеру, связанному с откатом и повторным выполнением операции undo/redo. Каждая операция do должна сопровождаться несколькими операциями отката undo . Например, вставка символа может автоматически приводить к прокрутке текстового окна (в некоторых текстовых редакторах эта операция применяется для улучшения внешнего вида текста). Отменяя эту операцию, вы возвращаете окно в прежнее положение. (В большинстве текстовых редакторов эта операция реализована неправильно. Досадно!) Чтобы выполнить обратную прокрутку (unscroll), нужно хранить несколько объектов класса Command в одном объекте класса Functor и выполнять их как одно целое. Функция-член Document::insertchar заталкивает объект класса MacroCommand в стек отката. В состав класса MacroCommand могут входить функции-члены Document: :Deletechar и window: :Scroll. Последнюю функцию можно связать с аргументом, запоминающим старое состояние. (И вновь связывание оказывается очень удобным инструментом.) в библиотеке Loki определен класс FunctorChain и вспомогательная функция Chain. template <class Funl, class Fun2> Fun2 chain( const Funl& funl, const Fun2& fun2) ; Реализация класса FunctorChain тривиальна- он хранит два функтора, а оператор FunctorChain::operatorO вызывает их один за другим. Функция Chai п завершает наше описание поддержки макрокоманд. Одно из достоинств такой реализации заключается в том, что список команд неограничен. Ни шаблонная функция BindFirst, ни функция chain не требуют вносить никаких изменений в класс Functor. Это свидетельствует о том, что вы сами можете разработать аналогичные функциональные возможности. 5.12. Первая практическая проблема: стоимость функций пересылки в принципе разработка шаблонного класса Functor завершена. Теперь мы сосредоточимся на вопросах его оптимизации, стремясь, чтобы он работал как можно эффективнее. Рассмотрим в классе Functor одну из перегрузок оператора (), пересылаюшую вызов интеллектуальному указателю. Внутри класса Functor<R, TList> R operatorO(Parml pi, Parm2 p2) { return (*splmpl )(pi, p2); При каждом вызове оператора () создается ненужная копия каждого аргумента. Если классы Parml и Parm2 достаточно велики, это снизит производительность работы программы. Довольно странно, но даже если оператор Q класса Functor сделать подставляемым (inhne), компилятор будет по-прежнему тиражировать лишние копии аргументов, в задаче 46 книги Саттера (Sutter, 2000) описывается современная модификация языка, сделанная непосредственно перед его стандартизацией. Функциям пересылки было запрещено создавать копии (ehding for copy construction). Компилятору позволено создавать копии лишь для возвращаемого значения, поскольку возврат значений нельзя оптимизировать вручную. Помогут ли ссылки решить эту проблему? Используем следуюший код. внутри класса Functor<R, TList> R operatorO(parml& pi, Parm2& p2) { return (*splmpl )(pi, p2); Обычно преждевременная оптимизация нежелательна. Одна из причин заключается в том, что программисты не могут правильно определить, какую часть программы следует оптимизировать, а какую - нет (что еше важнее). Однако разработчики библиотек находятся в другой ситуации. Они не знают, будут или нет использованы их библиотеки в критически важных частях какого-то приложения, поэтому стремятся максимально оптимизировать библиотеку.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |