С++ - язык, который изучается постепенно.Двойная передача
                   Справочники Всё для создания сайта

Ссылки


Home
Бизнес
Справочники
Советы







Материалы книги получены с http://www.itlibitum.ru/

Двойная передача

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

Конечно, возможны силовые решения - например, запрятать в каждом экземпляре сведения о его типе. Однако более элегантное решение (и обычно более эффективное) решение носит название двойной передачи (double dispatch).

class Number {

protected:

// Диспетчерские функции для оператора +

virtual Number& operator+(const Integer&) = 0;

virtual Number& operator+(const Complet&) = 0;

// И т.д. для всех производных типов

public:

virtual Number& operator+(const Number&) = 0;

virtual Number& operator-(const Number&) = 0;

// И т.д.

};

class Integer : public Number {

private:

int I;

protected:

virtual Number& operator+(const Integer&);

virtual Number& operator+(const Complex&);

public

Integer(int x) : i(x) {}

virtual Number& operator+(const Number&);

// И т.д.

};

Number& Integer::operator+(const Number& n)

{

return n + *this; // Поменять местами левый и правый операнд

}

Number& Integer::operator+(const Integer& n)

{

// Ниже приведен псевдокод

if (i + n.i слишком велико для int) {

return ЦелоеСПовышеннойТочностью

}

else return Integer(i + n.i);

}

С этим фрагментом связана одна нетривиальная проблема, к которой мы вернемся позже, а пока сосредоточьте все внимание на концепции. Она похожа на стереограмму - чтобы скрытая картинка проявилась, вам придется расслабить глаза и некоторое время рассматривать код. Когда клиент пытается сложить два Integer, компилятор передает вызов Integer::operator+(), поскольку operator+(Number&) является виртуальным - компилятор правильно находит реализацию производного класса. К моменту выполнения Integer::operator+(Number&) настоящий тип левого операнда уже известен, однако правый операнд все еще остается загадкой. Но в этот момент наступает второй этап двойной передачи: return n + *this. Левый и правый операнды меняются местами, а компилятор приступает к поискам v-таблицы n. Однако на этот раз он ищет переопределение Number::operator+(Integer&), так как он знает, что *this в действительности имеет тип Integer.

Это приводит к вызову Integer::operator+(Integer&), поскольку типы обоих операндов известны и можно наконец произвести вычисления. Если вы так и не поняли, что же происходит, прогуляйтесь на свежем воздухе и попробуйте снова, пока не поймете. Возможно, вам поможет следующая формулировка: вместо кодирования типа в целой переменной мы определили настоящий тип Number с помощью v-таблицы.

Такое решение не только элегантно. Вероятно, оно еще и более эффективно, чем те, которые приходили вам в голову. Скажем, приходилось ли вам видеть код, генерируемый компилятором для конструкции switch/case? Он некрасив и вдобавок куда менее эффективен, чем последовательное индексирование двух v-таблиц.

Несмотря на всю элегантность, двойная передача довольно дорого обходится по объему кода и сложности:

1.   Если у вас имеется m производных классов и n операторов, то каждый производный класс должен содержать m*(n+1) виртуальных функций, да еще столько же чисто виртуальных заглушек в классе-предке. Итого мы получаем (m+1)*m*(n+1) диспетчерских функций. Для всех иерархий, кроме самых тривиальных, это довольно много.

2.   Если оператор не является коммутируемым (то есть ему нельзя передать повторный вызов с аргументами, переставленными в обратном порядке), это число удваивается, поскольку вам придется реализовать отдельные функции для двух вариантов порядка аргументов. Например, y/x - совсем не то же, что x/y; вам понадобится оператор / и специальная функция DivideInto для переставленных аргументов.

3.   Клиенты базового класса видят все устрашающие защищенные функции, хотя это им совершенно не нужно.

Тем не менее, в простых ситуациях двойная передача оказывается вполне разумным решением - ведь проблема, как ни крути, достаточно сложна. Специфика ситуации неизбежно требует множества мелких фрагментов кода. Двойная передача всего лишь заменяет большие, уродливые, немодульные конструкции switch/case более быстрой и модульной виртуальной диспетчеризацией.

Как правило, количество функций удается сократить, но при этом приходится в той или иной степени идти на компромисс с нашим строгим правилом - никогда не спрашивать у объекта, каков его настоящий тип. Некоторые из этих приемов рассматриваются ниже. Видимость производных классов для клиентов Number тоже удается ликвидировать минимальной ценой; об этом будет рассказано в главе 12. Как и во многих проблемах дизайна в С++, в которых задействованы матрицы операций, вам придется на уровне здравого смысла решить, стоит ли повышать модульность за счет быстродействия

или объема кода.


Назад    Содержание    Далее    

Home  Создание сайтов  Учебник по записи CD  Справочник Web дизайнера Самоучитель IE PHP и MySQL Компьютерные сети С++ E-mail me

Copyright 2007. Климов Александр. All Right Reserved.
Hosted by uCoz