Множественное наследование
В языке Си++ имеется возможность в качестве базовых задать несколько классов. В таком случае производный класс наследует методы и атрибуты всех его родителей. Пример иерархии классов в случае множественного наследования приведен на следующем рисунке.
10.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 дважды (см. 10.3).
10.3. Структура объекта StudentLibrarian.
Кроме того, что подобная ситуация отражает нерациональное использование памяти, никаких неудобств в данном случае она не вызывает. Возможную неоднозначность можно разрешить, явно указав класс:
StudentLibrarian* sp; // ошибка – неоднозначное обращение, // непонятно, к какому именно экземпляру // типа Person обращаться sp-Person::name(); // правильное обращение sp-Student::Person::name();
Тем не менее, иногда необходимо, чтобы объект базового класса содержался в производном один раз. Для этих целей применяется виртуальное наследование, речь о котором впереди.