166
{ reutrn n3.fn2(n1, *this); }
//
И т.д. для всех типов в первой позиции
// ‘this’ – неявный третий аргумент
virtual Number& fn2(Integer& n1, Integer& n2)
{
//
Настоящая реализация – известны все три аргумента
}
//
И т.д. для всех сочетаний
public:
// ‘this’ – заданный первый аргумент
virtual Number& fn(Number& n2, Number& n3)
{ return n2.fn1(*this, n3); }
};
Такой вариант,
как и двойная передача, обычно работает быстрее других предлагаемых архитектур.
Все делается за три элементарных переходя через v-таблицы, а это намного быстрее, чем хеширование
и просмотр таблиц. Однако он некрасив и быстро становится неуправляемым. Возьмите решение с
двойной передачей и вообразите, что его сложность начинает возрастать по экспоненте; вы получите
некоторое представление о том, с чем связано поддержание такой структуры.
Описанная методика применима и к гетероморфным иерархиям, хотя в результате у вас получится
«программа-кузнечик»: она совершает головокружительные прыжки из стороны в сторону. Если вы
проделаете нечто подобное в нетривиальной ситуации, в день сдачи программы прихватите пончиков
или коробку конфет и будьте необычайно любезны со своими коллегами. Даже если это оптимальное
решение, его наверняка сочтут… как бы это сказать… в лучшем случае, спорным.
Если функция
имеет больше двух аргументов, число сочетаний растет быстрее, чем грибы после
дождя. Организуя множественную передачу для нескольких аргументов, серьезно подумайте над
описанными ниже приемами группировки, уменьшающими количество сочетаний.
Группировка передач и преобразования
В реальной жизни редко встречаются уникальные реализации для всех
сочетаний левого и правого
операндов. Например, в любой операции с участием комплексного и какого-то другог числа результат
будет комплексным. Преобразование некомплексного аргумента в комплексный сокращает количество
диспетчерских функций. Процесс сокращения матрицы передач я описываю общим термином
группировка (clustering). На самом деле для большинства задач не существует элегантного,
универсального и притом головокружительно быстрого способа группировки. К тому же эти способы
практически никак не связаны с синтаксисом или идиомами С++. Они либо требуют знания типов
(тема, к которой мы вернемся в следующей главе), либо основаны на логике
if/then/else
или
switch/case
, которой мы пытаемся всячески избегать в этой части.
Существут два основных подхода:
1. Использовать иерархию классов для обслуживания нескольких
сочетаний различных типов
аргументов одной реализацией.
2. Сформировать
иерархию преобразований и преобразовать один или оба аргумента к более
универсальному типу, после чего выполнить передачу.
Их нетрудно спутать, но на самом деле они отличаются.
Группировка в базовых классах
Первый подход обычно связан с созданием специфической иерархии классов,
которая отображает
структуру групп. При этом диспетчерские функции поддерживаются только на высоких уровнях
иерархии классов. При поиске сигнатур компилятор автоматически «преобразует» производные классы
к промежуточным базовым классам. Такой вариант хорошо подходит лишь для не очень глубоких