Множественное наследование и виртуальные базовые классы
Класс называют непосредственным (прямым) базовым классом (прямой базой), если он входит в список базовых при определении класса. В то же время для производного класса могут существовать косвенные или непрямые предшественники, которые служат базовыми для классов, входящих в список базовых. Если некоторый класс Аявляется базовым для Ви Весть база дляС,то класс Вявляется непосредственным базовым классом для С, а класс А- непрямой базовый класс для С. Обращение к компоненту ха, входящему в А и унаследованному последовательно классами Ви С, можно обозначить в классе Слибо как А::ха, либо как В::ха. Обе конструкции обеспечивают обращение к элементу хакласса А.
Производные классы принято изображать ниже базовых. Именно в таком порядке их объявления рассматривает компилятор и их тексты размещаются в листинге программы. Класс может иметь несколько непосредственных базовых классов, т.е. может быть порожден из любого числа базовых классов, например:
class X1 { ... };class X2 { ... };class X3 { ... };class Y1: public X1, public X2, public X3 { ... };Наличие нескольких прямых базовых классов называют множественным наследованием.
Определения базовых классов должны предшествовать их использованию в качестве базовых. При множественном наследовании никакой класс не может больше одного раза использоваться в качестве непосредственного базового. Однако класс может больше одного раза быть непрямым базовым классом:
class X { ...; f (); ... };class Y: public X { ... };class Z: public X { ... };class D: public Y, public Z { ... };В данном примере класс Х дважды опосредовано наследуется классом D.
Проиллюстрированное дублирование класса соответствует включению в производный объект нескольких объектов базового класса. В нашем примере существуют два объекта класса Х,и поэтому для устранения возможных неоднозначностей вне объектов класса D нужно обращаться к конкретному компоненту класса Х, используя полную квалификацию: D::Y::X::f()или D::Z::X::f(). Внутри объекта класса D обращения упрощаются Y::X::f()или Z::X::f(), но тоже содержат квалификацию.
Чтобы устранить дублирование объектов непрямого базового класса при множественном наследовании, этот базовый класс объявляют виртуальным. Для этого в списке базовых классов перед именем класса необходимо поместить ключевое слово virtual. Например, класс Хбудет виртуальным базовым классом при таком описании:
class X {... f() ; ... };class Y: virtual public X { ... };class Z: virtual public X { ... };class D: public Y, public Z { ... };Теперь класс D будет включать только один экземпляр Х, доступ к которому равноправно имеют классы Y и Z.
Обратите внимание, что размеры производных классов при отсутствии виртуальных базовых равны сумме длин их компонентов и длин унаследованных базовых классов. “Накладные расходы” памяти здесь отсутствуют.
При множественном наследовании один и тот же базовый класс может быть включен в производный класс одновременно несколько раз, причем и как виртуальный, и как не виртуальный.
class X { ... };class Y: virtual public X { ... };class Z: virtual public X { ... };class B: virtual public X { ... };class C: virtual public X { ... };class E: public X { ... };class D: public X { ... };class A: public D, public B, public Y, public Z, public C, public E { ... };В данном примере объект класса А включает три экземпляра объектов класса Х: один виртуальный, совместно используемый классами B, Y, C, Z, и два не виртуальных относящихся соответственно к классам D и E. Таким образом, можно констатировать, что виртуальность класса в иерархии производных классов является не свойством класса как такового, а результатом особенностей процедуры наследования.
Возможны и другие комбинации виртуальных и невиртуальных базовых классов. Например:
class BB { ... };class AA: virtual public BB { ... };class CC: virtual public BB { ... };class DD: public AA, public CC, public virtual BB { ... };При использовании наследования и множественного наследования могут возникать неоднозначности при доступе к одноименным компонентам разных базовых классов. Простейший и самый надежный способ устранения неоднозначностей - использование квалифицированных имен компонентов. Как обычно, для квалификации имени компонента используется имя класса. Следующий пример иллюстрирует упомянутую неоднозначность и ее разрешение с помощью квалификационных имен компонентов: