Анимация
JavaScript
|
Главная Библионтека Все это выглядит прекрасно и может на самом деле работать, пока вы не попробуете выполнить такой код. void testFunctionCstd::string&, int); Functor<void, TYPELiST 2(std::string&, int)> cmd(testFunction); string s; cmdCs, 5); Ошибка! Компилятор споткнется на последней строке, выдав сообщение: "Ссылки на ссылки не допускаются!". (На самом деле сообщение может быть более таинственным.) Фактически такая конкретизация может преобразовать класс Parml в ссылку на объект класса std: :string. Следовательно, объект р1 может стать ссылкой на ссылку на объект класса std:: string, а это не допускается. К счастью, рещение этой проблемы существует. В главе 2 описан шаблонный класс TypeTrai ts<T>, определяющий группу типов, связанных с типом т. К ним относятся неконстантные типы (если тип т является константным), указатели (если тип т является указателем) и многие другие. Тип, который можно безопасно и эффективно передавать как параметр функции, называется ParameterType. В приведенной ниже таблице показана связь между типом, передаваемым классу TypeTraits, и внутренним определением типа ParameterType. т TypeTraits<T>:: ParameterType Тип и, если и - элементарный тип; в противном случае - const и & const и Тип и, если и - элементарный тип; в противном случае - const и & и & и & const и & const и & Замена аргументов функции пересылки типами, указанными в правом столбце, всегда корректна, причем она не вызывает никаких дополнительных затрат, связанных с копированием. Внутри класса Functor<R, TList> R operatorO С typename TypeTraits<Parml>::ParameterType pl; typename TypeTraits<Parm2>::ParameterType p2; return (*splmpl )(pl, p2); Еще пристнее то, что эти ссылки прекрасно работают в сочетании с подставляемыми функциями. Оптимизатор легче генерирует оптимальный код, поскольку для этого ему достаточно установить ссылки. Эта проблема также возникает при работе со стандартными механизмами связывания. Бьярн Страуструп представил Комитету по Стандартизации отчет об этом дефекте. Он предао-жил исправить этот недостаток, разрешив ссылаться на ссылки и обрабатывать результирующие ссылки как простые. В момент написания книги этот отчет был доступен на Web-странице http:: anubi s.dkuug.dk/j tcl/s c22/wg21/docs/cwg acti ve.html#106. 5.13. Вторая практическая проблема: распределение динамической памяти Оценим затраты, связанные с созданием и копироваршем функторов. Мы должны реализовать правильную семантику значений, но это связано с проблемой распределения динамической памяти. Каждый объект класса Functor содержит интеллектуальный указатель на объект, созданный с помощью оператора new. Для создания копии объекта класса Functor функция-член Functorlmpl::Clone выполняет глубокое копирование. Это особенно неприятно, если размер объектов очень важен. В больщинстве случаев класс Functor используется с указателями на функции и парами, состоящими из указателей на объекты и указателей на функции-члены. В типичных 32-разрядных системах эти объекты занимают от 4 до 20 байт соответственно (4 байт для указателя на объект и 16 байт для указателя на функцию-член). При использовании связывания размер конкретного функтора увеличивается примерно на величину связываемого аргумента (вследствие выравнивания блоков памяти его размер может еще ненамного вырасти). в главе 4 описаны эффективные механизмы распределения памяти для небольших объектов. Класс Functorlmpl и его наследники являются превосходнь[ми кандидатами на применение этих механизмов распределения памяти. Напомним, что один из способов применения механизма распределения памяти для небольших объектов заключается в создании класса, производного от шаблонного класса SmallObject. Использовать этот класс очень легко. Однако нам нужно добавить в класс Functor шаблонный параметр, отображающий потоковую модель в механизме распределения памяти. Это не сложно, поскольку большую часть времени используется аргумент, заданный по умолчанию. В приведенном ниже коде изменения вьщелены полужирным шрифтом. template < class R, class tl, template <class T> class ThreadingModel = default threading > class Functorlmpl : public Smallobject<ThreadingModel> public: ... как и раньше ... Все это наделяет класс Functorlmpl функциональными возможностями механизма распределения памяти. Аналогично в сам класс Functor добавляется третий шаблонный параметр. template < class r, class tl, template <class T> class ThreadingModel = bEFAULT THREADiNG * Естественно было бы ожидать, что указатель на функцию-член занимает 4 байт, как и указатель на обычную функцию. Однако указатели на методы фактически представляют собой размеченные объединения (tagged union). Они применяют множественное виртуальное наследование и виртуальные/невиртуальные функции. class Functor { private: передаем параметр ThreadingModel классу Functorlmpl std:;auto ptr<Functorlmpl<R, TL, ThreadingModel> plmpl ; Для того чтобы использовать класс Functor со стандартной потоковой моделью, третий шаблонный аргумент не нужен. Только если в приложении понадобятся функторы, поддерживаюшие несколько потоковых моделей, нужно явно указать параметр ThreadingModel. 5.14. Реализация операций Undo и Redo с помощью класса Functor в книге Gamma et al (1995) рекомендуется реализовывать операцию undo с помошью дополнительной функции-члена unexecute класса Command. Проблема заключается в том, что эту функцию невозможно выразить в обобшенном виде, поскольку отношение между некоей операцией и ее отменой (undo) нельзя предсказать. Решить эту задачу можно, если создать отдельный класс Command для каждой операции, выполняемой в приложении. Однако подход, основанный на применении класса Functor, ориентирован на использование одного класса, связываемого с разными объектами и вызовами функций-членов. Статья Ала Стивенса (Л1 Stevens) в журнале Dr. Dobbs Journal (Stevens, 1998) может оказать нам огромную помошь в изучении обобщенных реализаций операций undo и redo. Стивене создал обобщенную библиотеку операций undo/redo, которую следует тщательно изучить, независимо от того, будете вы применять класс Functor или нет. Это все, что нам следует знать о структурах данных. Основная идея операций undo и redo заключается в использовании стека отката (undo stack) и стека повтора операций (redo stack). Если пользователь выполнил некое действие, например, набрал букву на клавиатуре, в стек отката заталкивается новый функтор. Это означает, что функция-член Document::insertchar должна затолкнуть в стек отката действие, обратное по отношению к операции вставки символа (например, функцию-член Document::DeleteChar). Основная нагрузка переносится на функцию-член, которая действительно выполняет операцию отката, а в классе Functor информация о том, как именно следует выполнять эту операцию, не хранится. При необходимости можно затолкнуть в стек повторения операции функтор, состоящий из класса Document и указателя на функцию Document:: insertchar, связанных с фактическим символьным типом. Некоторые текстовые редакторы позволяют "повторный набор" (retyping). После того как вы что-то набрали на клавиатуре и выбрали в меню пункт Redo, повторяется блок введенных символов. Для этого прекрасно подходит связывание, реализованное в классе Functor, позволяющее хранить вызов функции-члена Document:: Insertchar для заданного символа, инкапсулируя такие вызовы в одном функторе. Кроме того, повторить нужно не только символ, набранный последним (это было бы не слишком впечатляющим достижением), но и всю последовательность символов, набранных после выполнения последней опера- 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 |