Множественное наследование (МН)
МН называется процесс создания произвольного класса из двух или более.
В этом случае произвольный класс наследует данные и функции всех своих базовых предшественников. Существенным для реализации МН является тот факт, что адреса объектов 2-ого и и последующих базовых классов не совпадают с адресом объекта произв. класса. Этот факт должен учитываться компилятором при преобразовании указателя на произвольный класс в указатель на базовый класс и наоборот.
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;...
};
Виртуальные базовые классы всегда конструируются перед невиртуальными, вне зависимости от их расположения в иерархии наследования. Например, в приведенной иерархии у класса TeddyBear (плюшевый мишка) есть два виртуальных базовых: непосредственный – ToyAnimal (игрушечное животное) и экземпляр ZooAnimal, от которого унаследован класс Bear:
class Character { ... }; // персонажclass BookCharacter : public Character { ... }; // литературный персонажclass ToyAnimal { ... }; // игрушка class TeddyBear : public BookCharacter, public Bear, public virtual ToyAnimal { ... };Эта иерархия изображена на рис. 18.5, где виртуальное наследование показано пунктирной стрелкой, а невиртуальное – сплошной.
Рис. 18.5. Иерархия виртуального наследования класса TeddyBear
Непосредственные базовые классы просматриваются в порядке их объявления при поиске среди них виртуальных. В нашем примере сначала анализируется поддерево наследования BookCharacter, затем Bear и наконец ToyAnimal. Каждое поддерево обходится в глубину, т.е. поиск начинается с корневого класса и продвигается вниз. Так, для поддерева BookCharacter сначала просматривается Character, а затем BookCharacter. Для поддерева Bear – ZooAnimal, а потом Bear.
При описанном алгоритме поиска порядок вызова конструкторов виртуальных базовых классов для TeddyBear таков: ZooAnimal, потом ToyAnimal.
После того как вызваны конструкторы виртуальных базовых классов , настает черед конструкторов невиртуальных, которые вызываются в порядке объявления: BookCharacter, затем Bear. Перед выполнением конструктора BookCharacter вызывается конструктор его базового класса Character.
Если имеется объявление:
TeddyBear Paddington;то последовательность вызова конструкторов базовых классов будет такой:
ZooAnimal(); // виртуальный базовый класс Bear ToyAnimal(); // непосредственный виртуальный базовый класс Character(); // невиртуальный базовый класс BookCharacter BookCharacter(); // непосредственный невиртуальный базовый класс Bear(); // непосредственный невиртуальный базовый класс TeddyBear(); // ближайший производный класспричем за инициализацию ZooAnimal и ToyAnimal отвечает TeddyBear – ближайший производный класс объекта Paddington.
Порядок вызова копирующих конструкторов при почленной инициализации (и копирующих операторов присваивания при почленном присваивании) такой же. Гарантируется, что деструкторы вызываются в последовательности, обратной вызову конструкторов.