Множественное наследование (МН)

МН называется процесс создания произвольного класса из двух или более.

В этом случае произвольный класс наследует данные и функции всех своих базовых предшественников. Существенным для реализации МН является тот факт, что адреса объектов 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, где виртуальное наследование показано пунктирной стрелкой, а невиртуальное – сплошной.

Множественное наследование (МН) - student2.ru

Рис. 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.

Порядок вызова копирующих конструкторов при почленной инициализации (и копирующих операторов присваивания при почленном присваивании) такой же. Гарантируется, что деструкторы вызываются в последовательности, обратной вызову конструкторов.

Наши рекомендации