|
Программирование >> Полиморфизм без виртуальных функций в с++
paw->f(); вызывается BW::f( pbw->f(); вызывается AW::g( Как обычно, одна и та же виртуальная функция вызывается независимо от типа указателя на объект. Именно это и позволяет ра.зличным классам добавлять Чтобы поддержать смешанный стиль, необходимы два условия: □ возможность замещать виртуальные функции базового класса из двух различных производных классов; в противном случае существенные части реализации должны наследоваться только по одной цепочке, как в примере slist set из раздела 13.2.2; □ возможность определить, какая функция замещает виртуальную, и перехватить некорректные замещения; иначе пришлось бы полагаться па зависимость от порядка или разрешение во время выполнения. Рассмотрим приведенный выше пример. Прсд1Юложим, что в классе W есть виртуальные функции f () и g (): class w { . . . virtual void f(); virtual void g(); a в каждом из AW и BW за-мещаюище одну из них: class AW : public virtual W { . . . void g(); class bw : public virtual w { . . . void f(); class CW : public SW, public BW, public virtual W { ... Тогда CW можно использовать так: CW* pew = new CW; AW* paw = pew; BW* pbw = pew; void fffO { pew->f(); вызывается BW::f() pcw->g(); вызывается AW::g() V { fO, X } в ( FO, X } с ( ) D { д() } Рис. 12.5 Отметим, что доминирование определяется по отношению к любым именам, а не только к функциям. Провило доминирования необходимо для применения виртуальных функций, ибо именно оно определяет, какая функция будет исполняться при виртуальном вызове. Опыт показывает, что оно столь же хорошо подходит и для невиртуальных функций. При использовании ранних версий компилятора, где правило доминирования к невиртуальным функциям не применялось, возникали ошибки, и приходилось писать плохо структурированные программы . Для разработчика компилятора правило доминирования - обычное правило поиска и.мени, применяемое для того, чтобы понять, сушествует ли уникальная функциональность к обп1ему базовому классу и пользоваться тем, что добавил другой класс. Естественно, при проектировании производных классов это следует иметь в виду, а иногда необходимо кое-что знать о классах, наследующих из тех же базовых. Чтобы замещение виртуальных функций вдоль различных ветвей было возможным, необходимо выработать правило о том, какие сочетания замещающих функций допустимы, а какие должны считаться ошибкой. При обращении к виртуальной функции нужно, чтобы вызывалась одна и та же реальная функция независимо от того, как задан объект класса. Мы с Эндрю Кенигом сформулировали единственное, на наш взгляд, правило, которое гарантирует такое поведение: Имя В: : f доминирует над именем А: : f, если его класс А является для класса В базовым. Если одно имя имеет более высокий приоритет по сравнению с другим, то при выборе между ними не возникает неоднозначности; используется доминирующее имя. Например: class V { public: int f(); int x; }; class В : public virtual V { public: int f(); int x; }; class С : public virtual V { }; class D : public B, public С { void g(); }; void D::g() { X++; правильно: B::x доминирует над V::x f(); правильно: B::f() доминирует над V::f() Графически это выглядит ток, кок показано но рис. 12.5. функция, которую можно поместить в таблицу виртуальных функций. Более слабое правило не гарантировало бы этого, а более сильное привело бы к запрету осмысленных вызовов. Правила для абстрактных классов и правило доминирования гарантируют, что объекты можно создавать только для таких классов, которые предоставляют полный и непротиворечивый набор сервисов. Без этих правил при работе над нетривиальными проектами у программиста было бы мало надежд избежать серьезных ошибок во время выполнения. 12.4. Модель размещения объекта в памяти Множественное наследование усложняет модель объекта в двух отношениях: □ у объекта может быть несколько таблиц виртуальных функций; □ при просмотре таблицы виртуальных функций должен сушествовать способ найти подобъект, соответствующий классу, из которого взята замещающая функция. Рассмотрим пример: class А { public: virtual void f(int); class В { public: virtual void f(int); virtual void g() ; class С : public A, public В { public: void f(int); Объект класса С мог бы выглядеть в памяти, как показано на рис. 12.6. Две таблицы виртуальных функций vtbl необходимы потому, что в программе могут быть объекты классов А и В наряду с объектами класса С, в которых есть части, соответствующие классам А и В. Получая указатель на В, надо уметь вызывать виртуальную функцию, не зная, что такое В - настоящий объект класса В , часть в, принадлежащая объекту класса С или еще какой-то объект, включающий В. Поэтому у каждого В должна быть своя vtbl, доступ к которой осуществляется одинаково во всех случаях. Часть delta необходима потому, что, коль скоро vtbl найдена, должна быть вызвана функция именно из того подобъекта, где она определена. Например, при вызове д() для объекта С нужно, чтобы указатель this был направлен на подобъект В объекта С, тогда как при вызове f () для С нужно, чтобы this указывал на весь объект С.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |