Простое и множественное наследование

Наследование

 

Наследование - это порождение производных классов от базовых классов, причем производный класс наследует элементы базовых классов и имеет собственные элементы.

 

Производный класс

Синтаксис определения производного класса:

class cl3: atr1 cl1[, atr2 cl2]//cl1 - имя базового класса

{ //atr1 - атрибут наследования (private, public, protected)

... }

Атрибут наследования осуществляет управление доступом к элементам базового класса внутри производного класса. По умолчанию атрибут наследования - private.

Схема управления доступом в производном классе к наследуемым элементам имеет вид табл. 3.1.

Таблица 3.1.

Атрибут наследования Атрибут доступа в базовом классе Прямой доступ в производном классе к наследуемым элементам (по имени элем.)
private private protected public private, доступа нет варианты: 1 private, но доступ есть 2 private, но доступ есть 2
protected private protected public private, доступа нет 1 protected, доступ есть 3 protected, доступ есть 3
public private protected public private, доступа нет 1 protected, доступ есть 4 public, доступ есть 4

В файл заголовков производного класса директивой #include необходимо включить файл заголовков базового класса.

Схема управления доступом на условном примере представлена в таблице 3.2.

 

Таблица 3.2.

class cl1 //баз. класс { private: int a1; void x1(); protected: int b1;void y1(); public: int c1; void z1(); }; class cl2:public cl1//пр.кл { private: int a2; void x2(); protected: int b2;void y2(); public: int c2; void z2(); }; class cl3:private cl1//пр.кл { private: int a3; void x3(); protected: int b3;void y3(); public: int c3; void z3(); };
Прямой доступ в cl1: a1, x1(), b1, y1(), c1, z1() Прямой доступ в cl2: b1, y1(), c1, z1(), a2, x2(), b2, y2(), c2, z2() Прямой доступ в cl3: b1, y1(), c1, z1(), a3, x3(), b3, y3(), c3, z3()
Доступ из внеш.функций через объект (cl1 obj1): c1, z1() Доступ из внеш.функций через объект (cl2 obj2): c1, z1(), c2, z2() Доступ из внеш.функций через объект (cl3 obj3): c3, z3()

Простое наследование – это наследование, когда производный класс имеет непосредственно только один базовый класс.

Примеры простого наследования (схемы иерархии классов) представлены на рис. 3.1.

 

а) cl1 б) cl1 в) cl1 г) cl1

| / \ | / \

cl2 cl2 cl3 cl2 cl2 cl5

| / \ |

cl3 cl3 cl4 cl6

Рис. 3.1. Примеры простого наследования

Рассмотрим схему г). Пусть объявлен объект производного класса cl2: cl2 obj2;. тогда он будет иметь две части в памяти:

- элементы, унаследованные от cl1 (явное наследование);

- собственные элементы класса cl2.

Пусть объявлен объект производного класса cl3: cl3 obj3;. Он будет уже иметь три части, т.е.:

- элементы, унаследованные от cl1 (неявное наследование);

- элементы, унаследованные от cl2 (явное наследование);

- собственные элементы класса cl3.

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

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

class cl1 class cl2: public cl1

{ {

public: int tabn;

int tabn; void f()

... {

}; tabn=10; //доступ к собств. элементу

cl1::tabn=20;//доступ к наслед. элементу

};

Конструктор производного класса выполняет следующее:

- инициализирует собственные элементы;

- инициализирует наследуемые элементы.

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

class index class akt:public index

{ {

protected: protected: char tabn[10]; char* imf;

int max; char tabn[10];

public: char* imf;

index(int mk):max(mk) public:

{} akt(char* imf,int m):imf(im),index(m)

... {tabn[0]=’\0’; }

}; };

Деструктору производного класса не требуется явно иметь деструктор базового класса. Компилятор автоматически генерирует вызов базового деструктора.

Множественным наследованием называется наследование, когда производный класс имеет непосредственно более одного базового класса. Примером множественного наследования является схема на рис. 3.2.

 

cl1 cl2

\ /

cl3

Рис. 3.2. Пример множественного наследования

Пусть объявлен объект производного класса: cl3 obj3;. Он будет иметь в памяти три части:

- элементы, унаследованные от cl1 (явное наследование);

- элементы, унаследованные от cl2 (явное наследование);

- собственные элементы класса cl3.

Доступ к элементам производного класса (собственным и наследуемым) аналогичен доступу при простом наследовании.

Неоднозначность доступа возникает в следующих случаях:

- в производном классе и в одном из базовых классов имеются элементы с одинаковыми именами;

- в базовых классах имеются элементы с одинаковыми именами.

Например, имеется элемент int tabn в классах cl1 и cl2.

Для разрешения неоднозначности нужно использовать имя базового класса с операцией разрешения видимости (например, obj3.cl1::tabn=20;доступ к tabn в cl1).

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

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

При множественном наследовании базовый класс может быть объявлен в определении производного класса только один раз, однако он может наследоваться неявно более одного раза.

Пусть имеется схема иерархии классов (рис. 3.3).

cl1

/ \

cl2 cl3

\ /

cl4

Рис. 3.3. Схема иерархии классов

Объект obj4 класса cl4 имеет в памяти следующие части:

- элементы, унаследованные неявно от cl1 по левой ветви;

- элементы, унаследованные явно от cl2;

- элементы, унаследованные неявно от cl1 по правой ветви;

- элементы, унаследованные явно от cl3;

- собственные элементы класса cl4.

Объект класса cl4 содержит две копии элементов класса cl1, унаследованных неявно по левой и по правой ветвям. Возникает неопределенность при доступе к наследуемым элементам класса cl1 внутри класса cl4 или через объект класса cl4. Например, доступ к элементу tabn класса cl1 с помощью выражения obj4.tabn не верен. Для устранения необходимо указать путь (ветвь) наследования, т.е. имя базового класса соответствующей ветви с операцией разрешения видимости. Например, для доступа к tabn, унаследованной по левой ветви, необходимо выражение obj4.cl2::tabn, аналогично по правой ветви - obj4.cl3::tabn.

Пусть необходимо иметь в производном классе еще одну, третью копию элементов класса cl1, унаследованных явно. По двум ветвям элементы класса cl1 наследуются неявно, а по третьей ветви - явно. Это явная неоднозначность.

Базовый класс объявлен непосредственно в определении производного класса только 1 раз. Нельзя объявлять явно cl1 в cl4, так как cl1 уже объявлен в cl4 неявно через cl2 и cl3.

Для устранения неоднозначности можно заменить явное наследование класса cl1 на неявное, добавив пустой класс cl5 между cl1 и cl4. Изменение схемы иерархии классов - на рис. 3.4.

 

cl1 cl1

/ | \ / | \

cl2 cl3 | cl2 cl3 cl5

\ | / \ | /

cl4 cl4

Рис. 3.4. Изменение схемы иерархии классов

Виртуальные базовые классы

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

Виртуальный базовый класс имеет следующие свойства:

- объявляется добавлением ключевого слова virtual к атрибуту наследования базовых классов, производным от общего виртуального базового класса; пример для предыдущей схемы:

class cl1 {...}//виртуальный базовый класс

class cl2:virtual public cl1 {...}

class cl3:virtual public cl1 {...}

class cl4:public cl2,public cl3 {...}

- наследуется только одна копия виртуального базового класса в классах, порожденных от производных классов с общим виртуальным базовым классом;

- сохраняются все правила доступа к наследуемым и собственным элементам производного класса, но устранена неоднозначность при доступе к унаследованным элементам виртуального базового класса (доступ прямо по имени элемента), т.к. имеется только одна копия виртуального базового класса; например, доступ к элементу tabn класса cl1 возможен с помощью выражения obj4.tabn;

- устранена неоднозначность доступа при совпадении имен элементов в виртуальном базовом классе и производных от него, т.к. действует приоритет самого дальнего имени от вершины иерархии; например, при наличии элемента-функции vvod() в классах cl1 и cl2 оператор obj4.vvod() вызовет элемент-функцию vvod() класса cl2.