|
Программирование >> Многопоточная библиотека с принципом минимализма
списка типов и фиксированный базовый код. Это ценное, однако потенциально опасное качество - слишком большой код может потребовать слишком много времени на этапе компиляции, негативно повлиять на размер профаммы и обшее время ее выполнения. Для того чтобы установить офаничения на статическую рекурсию, нам нужно специализировать класс 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. Симметричность грубого подхода Штриховка пересечения двух фигур может зависеть от того, как именно пересекаются фигуры: прямоугольник накрывает эллипс или наоборот. Иногда это не имеет
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |