Множественное наследование
Рассмотрим две проблемы, которые возникают при множественном наследовании: конфликт имен между суперклассами и повторное наследование.
Конфликт имен происходит тогда, когда в двух или более суперклассах случайно оказывается элемент (переменная или операция) с одинаковым именем.
Пример. Определим абстракцию «Работающий студент». Для этого введем более общие абстракции «Работник» и «Студент». Абстракция «Работающий студент» будет наследовать компоненты обеих общих абстракций.
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; // Е
}