Теория и реализация языков программирования

       

Множественное наследование и виртуальные функции


При множественном наследовании виртуальные функции реализуются несколько сложнее. Рассмотрим следующие объявления:

class A { public: virtual void f(int); }; class B : { public: virtual void f(int); virtual void g(int); }; class C : public A, public B { public: void f(); };

Поскольку класс A порожден по классам A и B, каждый из следующих вызовов будет обращаться к C :: f() (считая, что каждый из трех указателей смотрит на объект класса C):

pa

f() pb
f() pc
f()

Рассмотрим, для примера, вызов pb

f(). При входе в C :: f указатель this должен указывать на начало объекта C, а не на часть B в нем. Во время компиляции вообще говоря не известно, указывает ли pb на часть B в C. Например, из-за того, что pb может быть присвоен просто указатель на объект B. Так что величина delta(B), упомянутая выше, может быть различной для разных объектов в зависимости от структуры классов, порождаемых из B и должна где-то хранится во время выполнения.

Следовательно, delta(B) должно где-то храниться и быть доступно во время исполнения. Поскольку это смещение нужно только для виртуального вызова функции, логично хранить его в таблице виртуальных функций.

Указатель this, передаваемый виртуальной функции, может быть вычислен путем вычитания смещения объекта, для которого была определена виртуальная функция, из смещения объекта, для которого она вызвана, а затем вычитания этой разности из указателя, используемого при вызове. Здесь значение delta(B) будет необходимо для поиска начала объекта (в нашем случае C), содержащего B, по указателю на B. Сгенерированный код вычтет значение delta(B) из значения указателя, так что хранится смещение со знаком минус, -delta(B). Объект класса C будет выглядеть следующим образом:


Рис. 9.21. 

Таблица виртуальных функций vtbl для B в C отличается от vtbl для отдельно размещенного B. Каждая комбинация базового и производного классов имеет свою таблицу vtbl. В общем случае объект производного класса требует таблицу vtbl для каждого базового класса плюс таблицу для производного класса, не считая того, что производный класс может разделять таблицу vtbl со своим первым базовым классом. Таким образом, для объекта типа C в этом примере требуется две таблицы vtbl (таблица для A в C объединена с таблицей для C oбъекта и еще одна таблица нужна для B объекта в C).



Содержание раздела