Программирование >>  Полиморфизм без виртуальных функций в с++ 

1 ... 94 95 96 [ 97 ] 98 99 100 ... 144


□ механизм вызова, который был бы таким же простым и эффективным, как поиск в таблице виртуальных функций;

□ набор правил, которые позволяли бы разрешать все неоднозначности на этапе компиляции.

Видимо, эта проблема разрешима, но мое впи.мание никогда ие было сосредоточено на ней столь длительное вре.мя, чтобы успеть продумать все детали.

Если я хотел, чтобы программа работала быстро, то для получения эквивалента таблицы виртуатьных функций требовалось, судя по всему, очень iMnoro па.мяти. Любое же репгение, не расходующее впустую много памяти па дублирование элементов таблиц, либо медленно осуществлялось, либо имело непредсказуемые характеристики эффективности, либо то и другое в.месте. Например, каждая реализация примера с Circle и Rectangle, не гребуюп1ая поиска подходян1ей функции во вре.мя выполнения, нуждалась в четырех указателях на функции. Добавим еще класс Triangle - и требуется уже девять указателей. Произведем от Circle класс Smiley - и необходи.мо целых шестнадцать указателей, хотя семь из иих следовало бы сэкоио.мить, используя элементы таблицы, где в.место Smiley стоит Circle.

Более того, массивы указателей иа функции, 3KBnBaj-ienTHbie таблица.м виртуальных функций, нельзя было построить, пока пе станет известна вся нрограм.ма, то есть на стадии konhiohobkh. Причина в том, что пе существует одного класса, которо.му принадлежат все за.мещающие функции. Да и ие может его быть, так как любая замещающая функция зависит от двух или более аргументов. В то время проблема была неразреши.ма, поскольку я не хотел вводить в язык средств, требующих нетривиальной поддержки со стороны компоновщика. Однако, как оказалось впоследствии, такой поддержки можно дожидаться года.\п1.

Меня беспокоил - хотя и не казался неразрешимым - вопрос о том, как разработать схему, синтаксис которой был бы однозначным. Очевидный ответ таков: все вызовы мультиметодов должны подчиняться таким же правилам, как и любые другие вызовы. Однако такое решение ускользало от меня, поскольку я искал какой-то специальный синтаксис и специальные правила для вызова мультиметодов. Например:

(r@s)->intersect(); вместо intersect(г,s)

гораздо лучшее решение было иредложепо Дугом Леа [Lea, 1991 J. Позволим явно объявлять аргументы со словом virtual. Например:

bool intersect{virtual const Shapeu, virtual const Shapes);

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

bool intersect{const Circles, const Rectangles) замещает {

. . .



И наконец, мультиметоды можно вызывать с помощью обычного синтаксиса вызова, как показано выше.

Мультиметоды - один из интересных вопросов вроде что, если... в С++. Мог бы я в то время спроектировать и реализовать их достаточно хорошо? Было бы их при.менение настолько важ1гы.м, чтобы оправдать потраченные усилия? Какую работу пришлось бы оставить незавершенной, чтобы хватило времени на проектирование и реализацию мультиметодов? Примерно с 1985 г. я сожалею, что не включил это средство в язык. Единственный раз я официально упо.мянул о муль-тиметодах иа конференции OOPSLA, когда выступил против языковой нетерпи-.мости и бессмысленности ожесточенных баталий по поводу языков [Stroustrup, 1990]. Я привел в качестве при.мера некоторые концепции из языка CLOS, которые мне чрезвычайно нравились, и особо выделил мультиметоды.

13.8.1. Когда нет мультиметодов

Так как же написать функцию, вроде intersect (), без мультиметодов?

До появления идентификации типа во время исполнения (см. раздел 14.2) единственным средством динамического ра,зрешеиия на основе типа были виртуальные функции. Поскольку нам нужно разрешение на основе двух аргументов, то виртуальную функцию придется вызывать дважды. В приведенно.м выше при-.мере с Circle и Rectangle есть три возможных статических типа аргумента, поэтому можно предоставить три виртуальные функции:

class Shape { . . .

virtual bool intersect(const Shapes) const =0; virtual bool intersect(const Rectangles) const =0; virtual bool intersect(const Circles) const =0;

В производных классах эти функции замещаются:

class Rectangle { . . .

bool intersect(const Shapes) const; bool intersect(const Rectangles) const; bool intersect(const Circles) const;

Любой вызов intersect {) разрешается вызовом подходящей функции из классов Circle или Rectangle. Затем надо позаботиться о то.м, чтобы функция, прини.мающая неконкретизированный аргумент типа Shape, вызывала другую виртуальную функцию для за.мещеиия его более конкретны.м:

bool Rectangle::intersect(const Shapes s) const {

return s.intersect(*this); *this - это Rectangle: разрешаем в зависимости от s

bool Circle::intersect(const Shapes s) const



return S.intersect{*this); *this - это Circle: разреиаем в зависимости от s

Остальные функции intersect () просто выполняют то, что от них требуется, зная типы обоих аргументов. Замети.м, что для применения такой техники необходима лишь первая функция Shape: : intersect (). Оставшиеся две функции в классе Shape - просто оптимизация, возможная, если о производном классе все известно во время проектирования базового.

Этот прием получил название двойной диспетчеризации и впервые был описан в работе [Ingalls, 1986]. Для С++ данный метод плох тем, что при добавлении класса в иерархию необходимо изменять уже сушествуюшие классы. Производный класс, например Rectangle, должен знать обо всех своих братьях , иначе не удастся составить правильный набор виртуальных функций. Так, при добавлении класса Triangle придется модифицировать и Rectangle, и Circle, а также - если желательна показанная выше оптимизация - и Shape.

class Rectangle : public Shape ( . . .

bool intersect{const Shapes); bool intersect(const Rectangles); bool intersect(const Circles); bool intersect(const Triangles);

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

Иной способ - хранить в объектах определенный идентификатор типа и после его проверки выбирать, какую функцию вызывать. Использование функции typeid!) для идентификации типа во время выполнения (см. раздел 14.2.5) -одна из возможностей. Можно завести структуру, где хранятся указатели на функции, и использовать для доступа к ней идентификатор типа. Достоинство данного метода в том, что базовый класс ничего не должен знать о сушествовании производных. Например, при наличии подходящих определений функция

bool intersect(const Shape* sl, const Shape* s2) {

int i = find index(sl.type id(),s2.type id()); if (i < 0) error( плохой индекс ); extern Fct table* tbl; Fct f = tbl[i]; return f (sl,s2);

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



1 ... 94 95 96 [ 97 ] 98 99 100 ... 144

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика