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


Класс называют непосредственным (прямым) базовым классом (прямой базой), если он входит в список базовых при определении класса. В то же время для производного класса могут существовать косвенные или непрямые предшественники, которые служат базовыми для классов, входящих в список базовых. Если некоторый класс A является базовым для B и B есть база для C, то класс B является непосредственным базовым классом для C, а класс A - непрямой базовый класс для C (рис. 8.2). Обращение к компоненту ха, входящему в A и уна­следованному последовательно классами B и C, можно обозначить в классе с либо как А::ха, либо как В::ха. Обе конструкции обеспечи­вают обращение к элементу ха класса A.

Рис. 8.2. Прямое и косвенное наследование классов

 

Иерархию производных классов удобно представлять с помощью направленного ациклического графа (НАГ), где стрелкой изображают отношение "производный от". Производные классы принято изображать ниже базовых. Именно в таком порядке их объявления рассматривает компилятор и их тексты размещаются в листинге программы. Класс может иметь несколько непосредственных базовых классов, т.е. может быть порожден из любого числа базовых классов, например,

class X1 {…};

class X2 {…};

class X3 {…};

class Y1: public X1, public X2, public X3 {…};

 

Наличие нескольких прямых базовых классов называют множественным наследованием. При множественном наследовании никакой класс не может больше одного раза использоваться в качестве непосредственного базового. Однако класс может больше одного раза быть непрямым базовым классом:

class X{ public: int a ; int 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 обращения упрощаются: У::Х::f() или Z::X::f(), но тоже содержат квалификацию.

В качестве примера рассмотрим класс Heir из предыдущего параграфа. Он является производным от классов Square и ClsEllipse. В свою очередь Square и ClsEllipse являются производными от класса Figura. Направленный ациклический граф для класса Heir будет выглядеть следующим образом.

Figura Figura

Square ClsEllipse

Heir

 

Как видим в класс Heir входит два объекта типа Figura. Это значит, что в Heir будет две копии всех не статических компонентных данных класса Figura и компонентным функциям этого класса можно будет обратиться, например, либо через Heir::Square::Figura::display(), либо через Heir::ClsEllipse::Figura::display().

Чтобы устранить дублирование объектов непрямого базового класса при множественном наследовании, этот базовый класс объявляют виртуальным. Для этого в списке базовых классов перед именем класса необходимо поместить ключевое слово virtual. Например, класс Х будет виртуальным базовым классом при таком описании:

 

class X { public: int a ; int f();};

class Y: virtual public X{…};

class Z: virtual public X{…};

class D: public Y, public Z{…};

 

Теперь класс D будет включать только один экземпляр X, доступ к которому равноправно имеют классы 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{…};

 

В данном примере объект класса A включает три экземпляра объектов класса X: один виртуальный, совместно используемый классами B, Y, C, Z, и два не виртуальных относящихся соответственно к классам D и E. Таким образом, можно констатировать, что виртуальность класса в иерархии производных классов является не свойством класса как такового, а результатом особенностей процедуры наследования.

Возможны и другие комбинации виртуальных и не виртуальных базовых классов, например:

class ВВ{…};

class АА: virtual public ВВ{…};

class CC: virtual public ВВ{…};

class DD: public AA, public CC, virtual public BB{…};

 

Соответствующий НАГ имеет вид:

При использовании наследования и множественного наследования могут возникать неоднозначности при доступе к одноименным компонентам разных базовых классов. Простейший и самый надежный способ устранения неоднозначностей - использование квалифицированных имен компонентов. Как обычно, для квалификации имени компонента используется имя класса. Следующий пример иллюстрирует упомянутую неоднозначность и ее разрешение с помощью квалифицированных имен компонентов:

class X { public: int d; … };

class Y { public: int d; … };

class Z: public X, public Y {

public:

int d;

void f(){d - X::d + Y::d;}

};



Дата добавления: 2020-12-11; просмотров: 388;


Поиск по сайту:

Воспользовавшись поиском можно найти нужную информацию на сайте.

Поделитесь с друзьями:

Считаете данную информацию полезной, тогда расскажите друзьям в соц. сетях.
Poznayka.org - Познайка.Орг - 2016-2024 год. Материал предоставляется для ознакомительных и учебных целей.
Генерация страницы за: 0.01 сек.