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

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


о типах значений, возвращаемых замещающими функциями, оказывалось в русле моих усилий сделать профаммирование на С++ более безопасным, простым и декларативным. Данное средство не только устраняло много обычных приведений типов, но также препятствовало искушению злоупотребить новыми динамическими приведениями, которые обсуждались в то же самое время (см. раздел 14.2.3).

Рассмотрев несколько вариантов, мы разрешили замещение В* на D* и В& на D&, где В - достижимый ба,зовый класс для D. Кро.ме того, можно добавлять или убирать const, если это безопасно. Не допускались такие технически воз.можиые преобразования, как D в достижимый базовый класс В, D - в класс X, для которого есть преобразование из D, int* в void*, double в int и т.д. На.м казалось, что преимущества подобных преобразований не перевешивают стоимости реализации и потенциального запутывания пользователей.

13.7.1. Ослабление правил аргументов

По моему опыту, ослаблению правила замещения для типа возвращаемого значения неизменно сопутствует неприемлемое эквивалентное предложение ослабить правила для типов аргу.ментов. Например:

class Fig { public:

virtual int operator==(const Fig&); . . .

class ColFig : public Fig { public:

Предполагается, что ColFig::operator==() замещает Fig::operator=={) {не разрешено в С++)

int operator== {const ColFig& x) , . . . private:

Color col;

int ColFig::operator==(const ColFig& x) {

return col == x.col && Fig::operator==(x);

Выглядит привлекательно и позволяет писать неплохой код, например:

void f(Fig£c fig, ColFig& cfl, ColFig& cf2) {

if (fig==cfl) { сравнить объекты Fig ...

} else if (cfl==cf2) { сравнить объекты ColFig ...



К сожалению, такое написание ведет и к неявному нарушению системы типов:

void g{Fig& fig, ColFig& cf) {

if {cf==fig) { что сравнивается? ...

Если ColFig: : operator== () замешает Fig: : operator== (), то при выполнении сравнения cf==f ig будет вызван оператор ColFig: :operator== () с аргументом типа Fig. Это неудачно, так как ColFig: : operator== () обращается к члену col, а в классе Fig нет такого члена. Если бы для ColFig: : operator== () было бы решено изменить его аргумент, то испортилась бы память. Я рассматривал такой сценарий, когда только начинал проектировать правила для виртуальных функций, и счел его неприемлемым.

Если бы данное разрешение было позволено, пришлось бы проверять каждый аргумент виртуальной функции во время выполнения. Устранить такие проверки во время оптимизации кода было бы нелегко. Без проведения глобального анализа мы никогда не узнаем, не объявлен ли объект в каком-то другом файле, причем с типом, для которого замешение потенциально опасно. Затраты, связанные с такой проверкой, довольно сушественны. Кро.ме того, если каждый вызов виртуальной функции может в принципе возбуждать исключение, то пользователю надо быть к этому готовым. Я счел, что не смогу пойти на такой шаг.

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

class Figure { public:

virtual int operator=={const Figures); ...

class ColFig : public Figure { public:

int operator=={const Figures x); ... private:

Color col;

int ColFig::operator=={const Figures x); {

if {Figure::operator=={x)) {

const ColFig* pc = dynamic cast<const ColFig*>(&x); if {pc) return col == pc->col;

return 0;



При таком подходе проверяемое во время выполнения приведение с помощью dynamic cast (см. раздел 14.2.2) служит дополпеиие.м кослабленны.м правилам замещения. Ослабление позволяет безопасно и декларативно работать с типами возвращаемых значений; dynamic cast не препятствует явной и относительно безопасной работе с типа.ми аргументов.

13.8. Мультиметоды

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

class Shape { ...

class Rectangle : public Shape { . . .

class Circle : public Shape { . . .

Как можно было бы спроектировать функцию пересечения двух фигур intersect (), которая бы корректно вызывалась для обоих своих аргументов? Например:

void f{Circles с. Shapes si. Rectangles r. Shapes s2) {

intersect {r,c); intersect {c,r) ,-intersect{c,s2); intersect(si,r); intersect(r,s2); intersect{sl,c); intersect(si,s2);

Если r и s относятся соответственно к Circle и Shape, то хотелось бы реализовать intersect четырьмя функциями:

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

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



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

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