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

1 ... 83 84 85 [ 86 ] 87 88 89 ... 144


vptr......

часть А

vptr часть в

часть с

Рис.

12.6

vtbl для А и с:

&С: :f

-.. vtbl для в:

&С: :f

-delta (В)

&В: :g

Если объект размещен в памяти, как предложено выще, то вызов

void ff(B* pb) {

pb->f(2) ;

можно реализовать, например, таким кодом

/* сгенерированный код */ vtbl entry* vt = &pb->vtbl[index(f)]; (*vt->fct) ( (В*) ( (char*)pb+vt->delta) ,2) ,-

Такую стратегию я применял в первой реализации множественного наследования в Cfront. Ее достоинство в то.м, что она легко выражается на С и, следовательно, переносима. К тому же в сгенерированном коде нет ветвлений, а значит, он эффективно выполняется на мащинах с конвейерной архитектурой.

Возможен и другой подход, при котором в таблице виртуальных функций не надо хранить дельту для указателя this; в.место этого хранится указатель на исполняемый код. Если корректировка this не требуется, то указатель на vtbl в действительности указывает на экземпляр выполняемой виртуальной функции; если же this нужно подправить, то хранящийся в vtbl указатель содержит адрес кода, который его корректирует, а затем выполняет нужный экземпляр виртуальной функции. При такой схеме объявленный выще класс С будет представлен, как показано на рис. 12.7.

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

Код, корректирующий указатель this, обычно называют шлюзом (thunk). Это название восходит к первым реализациям Algol60, где такие небольшие фрагменты кода применялись для реализации вызова по имени.



delta (В)

vptr часть А

vptr часть в

часть с

&С: :f

&В: :д

this-=delta (В); до to С::f;

Рис. 12.7

Когда я проектировал и реализовывал множественное наследование, мне были известны только две описанные стратегии. Для проектирования языка они почти эквивалентны, но у реализации с помощью щлюзов есть одно полезное свойство: она не влечет никаких расходов времени и памяти в случае одиночного наследования, а это в точности согласуется с правило.м нулевых издержек (см. раздел 4.5). Обе стратегии реализации обеспечивают приемлемую производительность во всех изученных мною случаях.

72.4.7. Размещение в памяти объекта виртуального базового класса

Почему виртуальный базовый класс был назван именно так? Часто я ухожу от ответа, отшучиваюсь, что виртуальный - значит волшебный , и приступаю к более важным вопросам. Однако есть объяснение и получше. Оно родилось в спорах с Дугом Макилроем задолго до первой публичной презентации множественного наследования в С++. Виртуальная функция - это функция, которая отыскивается с помощью косвенного вызова через объект. Аналогично объект, представляющий виртуальный базовый класс, не имеет фиксированного места в производных от него классах и, стало быть, тоже должен находиться с помоп1ью некоторой формы косвенности. Кроме того, базовый класс определяется как неименованный член. Поэтому, если бы были разрешены виртуальные члены-данные, виртуальный базовый класс являлся бы примером такого члена. Я хотел реализовать виртуальные базовые классы именно в тако.м стиле. Напри.мер, если есть класс X с виртуальны.м базовым классом V и виртуальной функцией f, в результате получается то, что изображено на рис. 12.8, вместо оптимизированной реализации в Cfront (см. рис. 12.9).

На этих рисунках &Х: : Vob j - смещение объекта, представляющего в X часть, относящуюся к виртуальному базовому классу V. Первая модель является более чистой и общей. Она требует при выполнении чуть больше времени, чем оптимизированная модель, зато эконо.мит память.

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




vtbl:

&Х: : f

&Х::Vobj

X: :f :

Рис. 12.8

vptr .....

&Х: :Vobj- -

Vobj

vtbl:

iX: :f

X: :f:

Рис. 12.9

одно конкретное применение: идентификация типа объекта во время исполнения. Есть и другие способы добиться такого результата (см, раздел 14.2).

12.4.2. Виртуальные базовые классы и приведение типов

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

class А : public virtual complex { /* ... */ }; class В : public virtual complex { /* ... */ } ; class С : public A, public В { /* ... */ };

void f(complex* pi, complex* p2, complex* p3) {

ошибка: нельзя приводить из типа виртуальной базы ошибка: нельзя приводить из типа виртуальной базы ошибка: нельзя приводить из типа виртуальной базы

(А*)р1 (А*)р2 (А*)рЗ

Если имеется вызов

void g() {

f(new А, new В, new С);

то объект complex, на который указывает р1, вряд ли окажется в той же позиции относительно А, как и complex, на который указывает р2. Следовательно, приведение от



1 ... 83 84 85 [ 86 ] 87 88 89 ... 144

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