Анимация
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

списка типов и фиксированный базовый код. Это ценное, однако потенциально опасное качество - слишком большой код может потребовать слишком много времени на этапе компиляции, негативно повлиять на размер профаммы и обшее время ее выполнения.

Для того чтобы установить офаничения на статическую рекурсию, нам нужно специализировать класс StaticDispatcher для двух ситуаций, когда тип объекта Ihs в списке TypesLhs и тип объекта rhs в списке TypesRhs не найдены.

Первая ситуация (неверный объекг Ihs) возникает при вызове функции Go из класса StaticDispatcher, когда в качестве парамефа TypesList ему передается класс NullType. Это признак того, что во время поиска список TypesLhs оказался исчерпанным. (Напомним, что класс NullType используется в качестве признака конца списка типов.)

template <

class Executor, class BaseLhs, class BaseRhs, class TypesRhs, typename ResultType

>

class StaticDispatcher<Executor, BaseLhs, NullType, BaseRhs, TypesRhs, ResultType>

static void Go(BaseLhs& Ihs, BaseRhs& rhs, Executor exec) {

exec.OnErrorClhs, rhs);

Обработка ошибок элегантно делегируется классу Executor, который будет рассмотрен ниже.

Вторая ситуация (неверный объект rhs) возникает при вызове функции Dis-patchRhs из класса StaticDispatcher, когда в качестве параметра TypesRhs ему передается класс NullType.

tempi ate <

class Executor,

class BaseLhs,

class TypesLhs, class BaseRhs, class TypesRhs, typename ResultType

>

class StaticDispatcher<Executor, BaseLhs, TypesLhs, BaseRhs, NullType, ResultType>

public:

static void DispatchRhs(BaseLhs& Ihs, BaseRhsfi rhs. Executors exec)

exec.OnErrorClhs, rhs);

Теперь настало время обсудить, как нужно реализовать класс Executor, чтобы использовать преимущество механизма двойной диспетчеризации, определенного выше.

Класс StaticDispatcher лишь распознает типы. Обнаружив правильные типы и объекты, он передает их функции Executor:: Fi re. Чтобы различать вызовы этой



функции, класс Executor должен реализовать несколько перегрузок функции Fife. Например, класс Executor для закрашивания области пересечения двух фигур выглядит следуюшим образом.

class HatchingExecutor {

public:

Разные алгоритмы закрашивания области пересечения void FireCRectangle&, Rectangle*); void FireCRectangle&, Ellipse*); void Fire(Rectangle*, Poly&); void Fire(Ellipse&, Poly&); void Fire(Ellipse*. Ellipse*); void Fire(Poly*, Poly*);

функция для обработки ошибок void OnError(Shape*, Shape*);

Класс HatchingExecutor используется вместе с классом StaticDispatcher.

typedef StaticDispatcher<HatchingExecutor, shape,

TYPELIST 3(Rectangle, Ellipse, Poly)> Dispatcher; shape *pl = ...; shape *p2 = ...; HatchingExecutor exec; Dispatcher::Go(*pl, *p2, exec);

Этот код вызывает соответствующую перегрузку функции Fire из класса HatchingExecutor. Шаблонный класс StaticDispatcher можно рассматривать как механизм динамической перегрузки - он откладывает перефузку на период выполнения профаммы. Это значительно облегчает использование класса StaticDispatcher. Нужно лишь реализовать класс HatchingExecutor, имея в виду правила перефузки, а затем использовать класс StaticDispatcher в качестве черного ящика, применяющего правила перефузки во время выполнения программы.

Класс StaticDispatcher имеет побочный эффект - он распознает и перефужает неоднозначности на этапе компиляции. Допустим, что мы вместо функции HatchingExecutor:: Fi ге(Е1 lipse*, Poly*) объявили функцию HatchingExecutor: :Fire(shape*, Poly*). Вызов функции HatchingExecutor::Fire с параметрами типа Ellipse и Poly может породить неоднозначность - обе функции соответствуют одному и тому же вызову. Интересно, что класс StaticDispatcher также обнаруживает эту ошибку и выдает о ней такое же детальное сообщение. Класс StaticDispatcher полностью соответствует правилам перефузки, существующим в языке С++.

Что случится, если во время выполнения профаммы обнаружится ошибка - например, если в качестве одного из аргументов функции StaticDispatcher: :Go передать класс Circle? Как уже упоминалось, в таких ситуациях класс StaticDispatcher просто вызывает функцию Executor: :ОпЕггог, парамефами которой являются исходные (не преобразованные) объекты Ihs и rhs. Это значит, что в нашем примере функция HatchingExecutor::OnError(shape*, shape*) представляет собой модуль обработки ошибок. Эту функцию можно применять по своему усмофению - ее вызов означает, что класс StaticDispatcher приступает к поиску динамических типов.

Как указывалось в предыдущем разделе, при фубой реализации диспетчера наследование порождает новые проблемы. Следовательно, приведенная ниже конкретизация класса StaticDispatcher содержит ошибку.



typedef StaticDispatcher <

SomeExecutor,

Shape,

TYPELIST 4(Rectangle, Ellipse, Poly, RoundedRectangle)

>

MyDispatcher;

Если шаблонному классу MyDispatcher передается класс RoundedRectangle, он будет рассматриваться как класс Rectangle. Оператор dynamic cast<Rectangle*> успешно применяется к указателю на класс RoundedRectangle, а поскольку оператор dynami c cast<RoundedRectangle*> находится в цепочке проверок ниже, он никогда не будет выполнен. Правильная конкретизация выглядит следующим образом.

typedef StaticDispatcher <

SomeExecutor, Shape,

TYPELiST 4(RoundedRectangle, Ellipse, Poly, Rectangle)

>

Dispatcher;

Общее правило таково: наиболее отдаленные потомки базового типа должны находиться в начале списка типов.

Было бы прекрасно, если бы это преобразование выполнялось автоматически. Списки типов позволяют это сделать. У нас есть средство для обнаружения наследования во время компиляции (глава 2), причем списки типов можно упорядочить. Таким образом, можно воспользоваться статическим алгоритмом DerivedToFront, описанным в главе 3.

Для автоматической сортировки Списка нужно лишь модифицировать реализацию класса StaticDispatcher следующим образом.

template <...>

class StaticDispatcher

typedef typename DerivedToFronf<

typename TypeLhs::Head>::Result Head; typedef typename DerivedToFront<

typename TypesLhs::Tai1>::Result Tail; public:

... как и раньше ...

Обеспечив удобную автоматизацию, не забудьте, что мы получаем в результате программу, генерирующую код. Проблема, связанная с существованием зависимостей между классами, остается нерешенной. Несмотря на то что грубую реализацию мультиметодов очень легко выполнить, класс StaticDispatcher по-прежнему зависит от всех типов, входящих в иерархию. Его преимущества - это быстродействие (если количество классов в иерархии не слишком велико) и ненавязчивость (nonintrusiveness), которая выражается в том, что для использования класса StaticDispatcher не нужно модифицировать иерархию типов.

11.5. Симметричность грубого подхода

Штриховка пересечения двух фигур может зависеть от того, как именно пересекаются фигуры: прямоугольник накрывает эллипс или наоборот. Иногда это не имеет



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