Множественное наследование и виртуальные базовые классы
Класс называют непосредственным (прямым) базовым классом (прямой базой), если он входит в список базовых при определении класса. В то же время для производного класса могут существовать косвенные или непрямые предшественники, которые служат базовыми для классов, входящих в список базовых. Если некоторый класс 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;