|
Группировка передач и преобразования В реальной жизни редко встречаются уникальные реализации для всех сочетаний левого и правого операндов. Например, в любой операции с участием комплексного и какого-то другог числа результат будет комплексным. Преобразование некомплексного аргумента в комплексный сокращает количество диспетчерских функций. Процесс сокращения матрицы передач я описываю общим термином группировка (clustering). На самом деле для большинства задач не существует элегантного, универсального и притом головокружительно быстрого способа группировки. К тому же эти способы практически никак не связаны с синтаксисом или идиомами С++. Они либо требуют знания типов (тема, к которой мы вернемся в следующей главе), либо основаны на логике if/then/else или switch/case, которой мы пытаемся всячески избегать в этой части. Существут два основных подхода: 1. Использовать иерархию классов для обслуживания нескольких сочетаний различных типов аргументов одной реализацией. 2. Сформировать иерархию преобразований и преобразовать один или оба аргумента к более универсальному типу, после чего выполнить передачу. Их нетрудно спутать, но на самом деле они отличаются. Группировка в базовых классах Первый подход обычно связан с созданием специфической иерархии классов, которая отображает структуру групп. При этом диспетчерские функции поддерживаются только на высоких уровнях иерархии классов. При поиске сигнатур компилятор автоматически «преобразует» производные классы к промежуточным базовым классам. Такой вариант хорошо подходит лишь для не очень глубоких иерархий, поскольку при совпадении сигнатуры в двух базовых классах компилятор начнет кричать «Караул, неоднозначность!». class foo { ... }; class bar : public foo { ... }; class banana : public bar { ... }; void fn(bar&); void fn(foo&); fn(*(new banana)); // Неоднозначность! Ха-ха-ха! Компиляторы обожают подобные шутки, поскольку они могут ждать и не сообщать об ошибке до тех пор, пока им не встретится заветное сочетание типов. Если бы существовала перегрузка fn() для аргумента banana&, никаких проблем не возникло бы - компилятор всегда предпочитает точное совпадение преобразованию. Но тогда пропадает весь смысл группировки посредством автоматического преобразования к базовому классу. Отделение типов от иерархии классов Второй подход сопровождается мучительными логическими построениями. Перед выполнением передачи аргументы преобразуются от специализированных типов к более универсальным. Например, при любой операции, в которой учавствует комплексное число (Complex), второй аргумент заранее преобразуется к типу Complex. Тем самым из матрицы фактически исключается целая строка и столбец. Если ни один из аргументов не является комплексным, мы ищем среди них вещественный (Real); если он будет найден, второй аргумент также преобразуется в Real. Если не будут найдены ни Complex, ни Real, ищем Rational и т.д. Подобная иерархия преобразований - от Integer (или чего угодно) к Rational, затем Real и Complex - не совпадает с иерархией классов, поскольку было бы глупо порождать легкий Integer от тяжеловесного Complex. Кстати, весьма интересный вопрос: почему иерархии типов (в данном случае числовых) часто плохо укладываются в иерархии классов, основанных на общих свойствах? |
Copyright 2005. Климов Александр. All Right Reserved.