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

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

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

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

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.

Множественное наследование - student2.ru

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

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

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

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

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

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

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

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

Множественное наследование - student2.ru

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

Одним из механизмов задания такого совместного использования является виртуальный базовый класс. Виртуальный базовый класс в производном классе представлен одним и тем же (совместно используемым) объектом. Для задания виртуального наследования используется синтаксис следующего примера.

class Person { . . .};

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

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

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

Зависимость

Пример. Пусть управление температурой каждый объект класса Controller осуществляет в соответствии с задаваемым ему планом. План представим в виде экземпляра класса Plan.

class Plan;

class Controller{

. . .

void process (Plan& );

. . .

};

Класс Plan упомянут как часть описания функции-члена process; это дает нам основание сказать, что класс Controller пользуется ус­лугами класса Plan.

Отношение зависимости (использования) между классами означает, что изменение в спецификации одного класса может повлиять на другой класс, который его использует, причем обратное в общем случае неверно. Можно сказать, что один из классов (клиент) пользуется услугами дру­гого (сервера).

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

Инстанцирование

Пример. Представим, что нам необходимы стек целых чисел и стек контроллеров, управляющих температурой. Мы могли бы описать два стека:

class IntStack {

int stack[100];

. . .

};

class ControllerStack {

Controller* stack[100];

. . .

};

Другой, более разумный, подход – создать универсальный стек, который мог бы хранить элементы любого нужного нам типа. Для этого мы можем описать стек, содержащий указатели на нетипизированные элементы:

class Stack {

void* stack[100];

. . .
};

Однако это не безопасно с точки зрения типов. Никто не гарантирует нам, что пользователь не поместит в стек элемент одного типа, а взять захочет элемент другого типа.

Для реализации нашей идеи необходимо воспользоваться шаблоном или параметризованным классом. Шаблон служит для построения других классов и может быть пара­метризован другими классами, объектами или операциями.Использование шаблонов реализует в языке С++ особый тип полиморфизма – параметрический полиморфизм.

template <class Тype> class Stack {

Тype stack[100];

. . .

public:

void push (Тype);

Т рор ( );

. . .

};

Префикс template < class Тype > делает Тype параметром объявления, которому этот пре­фикс предшествует.

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

Объявим нужные нам стеки:

typedef Stack < int > IntStack // синоним класса стеков целых чисел

typedef Stack < Controller* > ControllerStack // синоним класса стеков

// контроллеров

IntStack IS; // стек для целых чисел

ControllerStack CS; // стек для контроллеров

Объекты IS и CS – это экземпляры совершенно различных классов, которые даже не имеют общего суперкласса. Тем не менее они получены из одного параметризованного класса Stack.

Инстанцирование безопасно с точки зрения типов. По правилам C++ бу­дет отвергнута любая попытка поместить в стек или извлечь из него что-либо, кроме целых чисел или указателей на экземпляры класса Controller, соответственно.

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

template <class Тype > Тype max(Тype x, Тype y){

return (x > y) ? x : y;

};

Теперь мы можем использовать один и тот же шаблон для целых и вещественных чисел.

int i, j, k;

double a, b, c;

. . .

k = max <int> (i, j);

c = max <double> (a, b);

Кроме того, возможно использовать этот шаблон и для объектов некоторого класса, если в нем определена операция ">".

Наши рекомендации