Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Class CReaderStudent: public CStudent, public CReader

Поиск

// «студент-читатель»

{

public:

CReaderStudent(/* параметры конструктора */):

CStudent(/*... */), CReader(/*... */)

{ /* тело конструктора */ }

// другие компоненты

...

};

Графически иерархию из нашего примера можно представить так, как показано на рисунке 1, где S – класс CStudent, R – класс CReader, а D – производный класс CStudentReader; стрелки показывают отношение «наследует от».

Объект всякого класса, входящего в классовую иерархию, содержит в себе экземпляры каждого базового класса (как прямого, так и непрямого). Например, объект класса CStudentReader будет включать как часть объект класса CStudent и объект класса CReader. Графически это можно представить следующей диаграммой(рис. 2).

Рис. 2

Особенностью множественного наследования в С++ является то, что любой класс может использоваться как косвенный базовый класс многократно; возможна также ситуация, когда класс одновременно является и прямым, и косвенным базовым классом. В этом случае объект производного класса будет включать сразу несколько объектов одного и того же базового класса[15]. Эта ситуация обусловливает одну из главных проблем множественного наследования – потенциальную неоднозначность. Она заключается в возможном конфликте имен компонент, унаследованных от базовых классов. Например, в приведенных выше классах CStudent и CReader может быть определена функция GetName(), возвращающая имя студента / имя читателя. Эта функция будет унаследована классом CStudentReader фактически дважды: первый раз из класса CStudent, второй раз из класса CReader. В результате ее не получится вызвать для объекта класса CStudentReader без дополнительной квалификации:

CStudentReader * pSR = &StudentReader;

StudentReader –> GetName(); // неоднозначность

StudentReader –> CStudent::GetName(); // нормально

StudentReader –> CReader::GetName(); // тоже нормально

Вызов с квалификацией выглядит менее читаемым и, что более важно, его применение создает ряд неудобств. В частности, если функция GetName виртуальная, а класс CStudentReader переопределяет ее, вызов с квалификацией сделает функцию невиртуальной.

Конфликт имен компонент независимых классов происходит не так часто, поэтому описанная проблема, возможно, и не так существенна. Однако, когда один и тот же класс выступает как базовый для другого класса несколько раз, эта проблема возникнет с вероятностью 100%. Классический случай – иерархия, представленная на рисунке 3, где класс A будет базовым для класса D два раза: один раз по цепочке наследования DBA, другой раз – по цепочке DCA.

Если использовать обычную схему невиртуального наследования, то каждый объект класса D будет содержать в себе объект класса A дважды и будет дважды наследовать все его компоненты. Фактически получится иерархия, показанная на рисунке 4.

А возможная схема размещения данных объекта класса D в памяти будет подобна приведенной на рисунке 5.

Рис. 5

Если в классе A есть компонент X, то в классе D появятся унаследованные компоненты A::B::D::X и A::C::D::X. При обращении к таким компонентам без их полной квалификации неизбежна ошибка компиляции. Дублирование унаследованных данных, кроме того, вызывает дополнительный расход памяти на объект класса D.

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

class A { /*... */ };

class B: virtual public A { /*... */ };

class C: virtual public A { /*... */ };

class D: public B, public C { /*... */ };

где классы B и C виртуально наследуют от класса A, за счет чего их наследник – класс D – косвенно наследует от класса A лишь один раз. В результате классовая иерархия приобретает первоначальный вид ромба (см. рис.3), без дублирования базового класса.

Размещение данных объекта класса D при виртуальном наследовании может быть другим (рис.6).

Рис. 6

Данные объекта класса A теперь присутствуют в единственном экземпляре, однако добавляются дополнительные указатели на них от данных объектов классов B и C.

Виртуальное наследование решает проблему потенциальной неоднозначности, однако его применение тоже не лишено трудностей. Первая проблема – вызов конструкторов виртуальных базовых классов. Конструктор любого класса, входящего в иерархию с виртуальным наследованием, должен явно вызывать конструкторы всех виртуальных базовых классов, где бы в иерархии этот класс не находился. Таким образом, разработчик очередного класса иерархии должен знать: какие аргументы требуют конструкторы виртуальных базовых классов. При этом отношение «быть виртуальной базой» передается при наследовании, т.е. все виртуальные базовые классы базового класса автоматически становятся таковыми и для производного класса. Еще одна проблема – доминирование виртуальных функций. Если виртуальный базовый класс содержит виртуальную функцию, которая переопределяется в его непосредственных производных классах, то ниже в иерархии вызов этой функции может быть неоднозначным.

Ниже дан пример, иллюстрирующий две названные выше проблемы виртуального наследования.

Пример



Поделиться:


Последнее изменение этой страницы: 2016-08-14; просмотров: 146; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.221.161.43 (0.006 с.)