Анимация
JavaScript
|
Главная Библионтека Для решения этой проблемы нужно не так уж много, однако до сих пор она не ликвидирована окончательно. Таким образом, при регистрации всех пар типов в классе BasicDispatcher следует проявлять осторожность."* 11.6.2. Логарифмический диспетчер и приведение типов Класс BasicDispatcher полезен, однако не вполне хорош. Зарегистрированная в нем функция, обрабатывающая пересечение между объектами классов Rectangle и Poly, должна принимать аргументы базового типа shape*. Предлагать клиентскому коду (реализации класса HatchRectanglePoly) привести ссылки на объекты класса Shape к правильному типу не совсем удобно и безопасно. С другой стороны, ассоциативный массив обратных вызовов не может хранить разные функции и типы функторов для каждого элемента, поэтому следует стремиться к их единообразному представлению. В параграфе 31 книги Мейерса "More Effective С++" (Meyers, 1996а) эта проблема уже обсуждалась. Преобразование "указатель на функцию - указатель на функцию" не подходит, поскольку после возвращения из функции FnDoubleDispatcher: :Add информация о статическом типе теряется, и к какому типу следует приводить параметр, неизвестно. (Попробуйте разобрать какой-нибудь код, и вы сразу поймете, в чем дело.) Мы решим задачу динамического.приведения типов с помощью простого обратного вызова функций (а не функторов). Таким образом, шаблонный аргумент Call-backTemplate является указателем на функцию. Решить задачу нам поможет трамплинная функция (trampoline function), также известная под названием санк (thunk). Трамплинные функции - это маленькие функции, выпол-шпощие небольшую настройку перед вызовами других функций. Обычно они используются разработчиками компиляторов языка С++ для реализации ковариантных типов возвращаемых значений и настройки указателей при множественном наследовании. Мы будем использовать трамплинную функцию, выполняющую соответствующее приведение типов, а затем вызывать функцию с подходящей сигнатурой, облегчая таким образом работу пользователей. Однако существует одна проблема: объект call-Ьаскмар теперь должен хранить два указателя на функции: один предоставляется пользователем, а другой является указателем на трамплинную функцию. Это снижает быстродействие программы. Вместо одного косвенного вызова через указатель у нас есть два. Кроме того, реализация усложняется. Для преодоления этого препятствия воспользуемся следующим фактом. Шаблон может получать, указатель на функцию в качестве параметра, лишенного типа. (В большинстве книг шаблонные параметры, лишенные типа, представляют собой целочисленные значения.) Вообще говоря, в качестве параметров, лишенных типа, шаблону можно передавать указатели на любые глобальные объекты, в том числе и функции. При этом должно выполняться единственное условие: функция, адрес которой является шаблонным аргументом, должна иметь внешнюю связь (external linkage). Статические функции можно легко превратить в функции с внешней связью, удалив ключевое слово static и поместив их в безымянное пространство имен. Например, в стандартном пространстве имен языка С++ можно объявить следуюшую функцию. static void FunC); Я убежден, что решение этой проблемы сушествует. Однако, увы, срок работы над книгой офаничен контрактом. в безымянном пространстве имен эта функция объявляется иначе. namespace { void FunС); Используя указатель на функцию в качестве шаблонного параметра, лишенного типа, мы отказываемся хранить его в ассоциативном массиве. Этот важный момент следует хорошо уяснить. Мы поступаем так потому, что компилятор обладает статической информацией об этом указателе. Следовательно, компилятор может зафиксировать адрес функции в трамплинном коде. Эту идею можно реализовать в новом классе, используюшем класс BasicDispathcer в качестве внутреннего буфера (back end). Новый класс FnDispatcher предназначен для диспетчеризации только функций, а не функторов. Он включает класс BasicDispatcher в свой закрытый раздел и предоставляет соответствуюшие интерфейсные функции. Шаблонная функция FnDispatcher: :Add имеет три шаблонных параметра. Два из них представляют собой левый и правый типы, для которых регистрируется диспетчеризация (классы ConcreteLhs и ConcreteRhs). Третий шаблонный параметр (callback) является указателем на функцию. Дополнительная функция FnDispatcher: :Add перефужает определенную выше шаблонную функцию Add, имеющую только два параметра. template <class BaseLhs, class BaseRhs = BaseLhs, ResultType = void> class FnDispatcher { BaseDispatcher<BaseLhs, BaseRhs, ResultType> backEnd ; public: template<class ConcreteLhs, class ConcreteRhs, ResultType (*callback)(ConcreteLhs&, ConcreteRhs&)> void Add() { struct Local CM. главу 2 { static ResultType Trapmoline(BaseLhs& Ihs, BaseRhs& rhs) { return call back( dynamic caSt<ConcreteLhs&>(lhs), dynamic cast<ConcreteRhs&>(rhs)); return backEnd .Add<ConcreteLhs, ConcreteRhs>( &Local::Trampoli ne); Используя локальную сфуктуру, мы определяем трамплинную функцию непосредственно внуфи функции Add. Трамплинная функция осуществляет приведение аргументов к соответствующим типам, а затем передает управление функции, на которую ссылается указатель обратного вызова. Функция Add добавляет трамплинную функцию в объект са11Ьаскмар с помощью функции Add объекта backEnd (определенной в классе BaseDispatcher). С точки зрения быстродействия трамплинная функция не создает дополнительных проблем. Хотя она выглядит как косвенный вызов, это не так. Как указывалось выще, компилятор фиксирует адрес, хранящийся в указателе обратного вызова, в коде функции Trampoline, поэтому второй косвенный вызов не возникает. Более изощренные компиляторы при малейшей возможности делают трамплинную функцию подставляемой. Использовать новую функцию Add легко, typedef FnDispatcher<Shape> Dispatcher; возможно в безымянном пространстве имен void HatchRectang1ePo1yCRectang1e& Ihs, Ро1у& rhs) Dispatcher disp; disp.Add<Rectang1e, Roly, Hatch>C); Благодаря функции-члену Add класс FnDispatcher легко использовать. Он также позволяет при необходимости обращаться к функции Add, определенной в классе BaseDispatcher. 11.7. Класс FnDispatcher и симметрия Благодаря динамизму класса FnDispatcher добавить в него поддержку симметрии намного проще, чем в статический класс StaticDispatcher. Для этого достаточно зарегистрировать две трамплинные функции: одну - для вызова исполняющей функции при обычном порядке следования аргументов, а вторую - для перестановки параметров перед вызовом. Добавим в функцию Add новый шаблонный параметр. template <c1ass BaseLhs, class BaseRhs = BaseLhs, typename ResultType = void> class FnDispatcher { template <c1ass ConcreteLhs, class ConcreteRhs, ResultType С*саПback)(ConcreteLhs*, ConcreteRhs*), bool symmetrio bool Add() struct Local { ... трамплинная функция остается прежней ... static void Trampoline(BaseRhs* rhs, BaseLhs* Ihs) { return Trampoline(lhs, rhs); Add<ConcreteLhs, ConcreteRhs>C*Local::Trampoline); if (symmetric) Add<ConcreteRhs, ConcreteLhs>(*Local: :TrampolineR) ; функцию FnDispatcher: :Add нельзя использовать только для регистрации динамически загружаемых функций. ДЛЯ того чтобы это стало возможно, в программу нужно внести небольшие изменения. 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 |