Язык программирования C++

         

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


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


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();

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


Содержание раздела