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

 

Рассмотрим две проблемы, которые возникают при множественном наследовании: конфликт имен между суперклассами и повторное наследование.

Конфликт имен происходит тогда, когда в двух или более суперклассах случайно оказывается элемент (переменная или операция) с одинаковым именем.

Пример. Определим абстракцию «Работающий студент». Для этого введем более общие абстракции «Работник» и «Студент». Абстракция «Работающий студент» будет наследовать компоненты обеих общих абстракций.

 

class Worker {

public:

int ID_profession; // код профессии

char* Name; // имя

};

class Student {

public:

int ID_university; // код университета

char* Name; // имя

};

class Student_Worker: public Student, public Worker { . . . };

 

Рассмотрим последовательность действий

 

Student_Worker He;

. . .

He.ID_profession; // правильно

He.Name; // неправильно – двусмысленно

 

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

 

He.Worker :: Name; // правильно

 

Повторное наследование возникает тогда, когда в многоуровневой наследственной иерархии какой-либо класс дважды является суперклассом для другого класса.

Продолжим пример с работающим студентом. Анализируя глубже полученную иерархию наследования, мы обнаружим, что и работник, и студент имеют ряд общих признаков, в частности, имя. Разумно ввести еще более общую абстракцию «Человек».

 

class Person {

public: char* Name; // имя

}

 

class Worker : public Person {

public: int ID_profession; // код профессии

}

 

class Student : public Person {

public: int ID_university; // код университета

}

 

Наследственная иерархия класса Student_Worker представлена на рис. 4.1.

 

 

Рис. 4.1 Наследственная иерархия Рис. 4.2 Ромбовидная структура

класса Student_Worker наследования

 

Для доступа к одной из копий унаследованного эле­мента необходимо воспользоваться явной квалификацией, т.е. добавить к его имени префикс в виде имени класса-источника.

 

He.ID_profession; // правильно

He.Name; // неправильно – двусмысленно

He.Person :: Name; // неправильно – двусмысленно

He.Worker :: Name; // правильно

He.Student :: Name; // правильно

 

Продолжая анализ полученной иерархии, заметим, что работающий студент имеет всего одно имя. В результате объект класса Student_Worker должен использовать единственную копию эле­мента Name, унаследованную от Person. В результате приходим к ромбовидной структуре наследования, когда повторяющийся суперкласс в производном классе представлен одним и тем же (совместно используемым) объектом (см. рис 4.2).

В С++ механизмом задания ромбовидной структуры наследования является виртуальное наследование, когда повторяющийся суперкласс объявляется «виртуальным базовым классом». Для задания виртуального наследования используется синтаксис следующего примера.

 

class Person { . . .};

class Worker : public virtual Person {. . .};

class Student : public virtual Person {. . .};

class Student_Worker: public Student, public Worker {. . . };

 

Задача. Укажите ошибочные строки в функции main среди отмеченных буквами А, Б, В, Г, Д, Е.

 

class Transport // Транспортное средство

{public: String Registration_Number; // регистрационный номер

};

class Land_Transport: public Transport { // Сухопутное транспортное средство

public: int Shaft; // ведущая ось

};

class Water_Transport: public Transport { // Водное транспортное средство

public: int Displacement; // водоизмещение

};

class Amphibia: public Land_Transport, public Water_Transport {}; // Амфибия

void main( ) {

Amphibia amph;

. . .

amph.Shaft; // А

amph.Displacement; // Б

amph.Water_Transport :: Displacement; // В

amph.Registration_Number; // Г

amph.Water_Transport :: Registration_Number; // Д

amph.Transport :: Registration_Number; // Е

}