Множественное наследование
В языке Си++ имеется возможность в качестве базовых задать несколько классов. В таком случае производный класс наследует методы и атрибуты всех его родителей. Пример иерархии классов в случае множественного наследования приведен на следующем рисунке.
Рис. 2. Иерархия классов при множественном наследовании.
В данном случае класс C наследует двум классам, A и B.
Множественное наследование – мощное средство языка. Приведем некоторые примеры использования множественного наследования.
Предположим, имеющуюся библиотечную систему решено установить в университете и интегрировать с другой системой учета преподавателей и студентов. В библиотечной системе имеются классы, описывающие читателей и работников библиотеки. В системе учета кадров существуют классы, хранящие информацию о преподавателях и студентах. Используя множественное наследование, можно создать классы студентов-читателей, преподавателей-читателей и студентов, подрабатывающих библиотекарями.
В графическом редакторе для некоторых фигур может быть предусмотрен пояснительный текст. При этом все алгоритмы форматирования и печати пояснений работают с классом Annotation. Тогда те фигуры, которые могут содержать пояснение, будут представлены классами, производными от двух базовых классов:
class Annotation{
public:
String GetText(void);
private:
String annotation;
};
class Shape{
public:
virtual void Draw(void);
};
class AnnotatedSquare : public Shape, public Annotation {
public:
virtual void Draw();
};
У объекта класса AnnotatedSquare имеется метод GetText, унаследованный от класса Annotation, он определяет виртуальный метод Draw, унаследованный от класса Shape.
При применении множественного наследования возникает ряд проблем. Первая из них – возможный конфликт имен методов или атрибутов нескольких базовых классов.
class A{
public:
void fun();
int a;
};
class B{
public:
int fun();
int a;
};
class C : public A, public B{};
При записи
C* cp = new C;
cp->fun();
невозможно определить, к какому из двух методов fun происходит обращение. Ситуация называется неоднозначной, и компилятор выдаст ошибку. Заметим, что ошибка выдается не при определении класса C, в котором заложена возможность возникновения неоднозначной ситуации, а лишь при попытке вызова метода fun.
Неоднозначность можно разрешить, явно указав, к которому из базовых классов происходит обращение:
cp->A::fun();
Вторая проблема заключается в возможности многократного включения базового класса. В упомянутом выше примере интеграции библиотечной системы и системы кадров вполне вероятна ситуация, при которой классы для работников библиотеки и для студентов были выведены из одного и того же базового класса Person:
class Person {
public:
String name();
};
class Student : public Person{
. . .
};
class Librarian : public Person{
. . .
};
Если теперь создать класс для представления студентов, подрабатывающих в библиотеке
class StudentLibrarian : public Student, public Librarian {
};
то объект данного класса будет содержать объект базового класса Person дважды (см. рисунок 3).
Рис. 3. Структура объекта StudentLibrarian.
Кроме того, что подобная ситуация отражает нерациональное использование памяти, никаких неудобств в данном случае она не вызывает. Возможную неоднозначность можно разрешить, явно указав класс:
StudentLibrarian* sp;
// ошибка – неоднозначное обращение,
// непонятно, к какому именно экземпляру
// типа Person обращаться
sp->Person::name();
// правильное обращение
sp->Student::Person::name();
Тем не менее, иногда необходимо, чтобы объект базового класса содержался в производном один раз. Для этих целей применяется виртуальное наследование, речь о котором впереди.
Базовый класс можно объявить виртуальным базовым классом, используя запись:
class Student : virtual Person { };
class Librarian : virtual Person { };
Гарантировано, что объект виртуального базового класса будет содержаться в объекте выведенного класса (см. рисунок 4) один раз. Платой за виртуальность базового класса являются дополнительные накладные расходы при обращениях к его атрибутам и методам наследования.
Рис. 4. Структура объекта StudentLibrarian при виртуальном
множественном наследовании.