|
Программирование >> Полиморфизм без виртуальных функций в с++
vtbl для А и с: &С: :f -.. vtbl для в:
Если объект размещен в памяти, как предложено выще, то вызов 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
Рис. 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
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. Следовательно, приведение от
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |