Анимация
JavaScript


Главная  Библионтека 

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

Для решения этой проблемы нужно не так уж много, однако до сих пор она не ликвидирована окончательно. Таким образом, при регистрации всех пар типов в классе 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