Класс называют непосредственным (прямым) базовым классом (прямой базой), если он входит в список базовых при определении класса. В то же время для производного класса могут существовать косвенные или непрямые предшественники, которые служат базовыми для классов, входящих в список базовых. Если некоторый класс А является базовым для В и В есть база для С, то класс В является непосредственным базовым классом для С, а класс А - непрямой базовый класс для С. Обращение к компоненту ха, входящему в А и унаследованному последовательно классами В и С, можно обозначить в классе С либо как А::ха, либо как В::ха. Обе конструкции обеспечивают обращение к элементу ха класса А.
Производные классы принято изображать ниже базовых. Именно в таком порядке их объявления рассматривает компилятор и их тексты размещаются в листинге программы. Класс может иметь несколько непосредственных базовых классов, т.е. может быть порожден из любого числа базовых классов, например:
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 {... };При использовании наследования и множественного наследования могут возникать неоднозначности при доступе к одноименным компонентам разных базовых классов. Простейший и самый надежный способ устранения неоднозначностей - использование квалифицированных имен компонентов. Как обычно, для квалификации имени компонента используется имя класса. Следующий пример иллюстрирует упомянутую неоднозначность и ее разрешение с помощью квалификационных имен компонентов:
class X { public: int d;... };class Y { public: int d;... };class Z: public X, public Y,{ public: int d;... d = X::d + Y::d;...};