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

Язык C++ / Множественное наследование

 

Если порожденный класс наследует элементы одного базового класса, то такое наследование называется одиночным. Однако, возможно и множественное наследование. Множественное наследование позволяет порожденному классу наследовать элементы более, чем от одного базового класса. Синтаксис заголовков классов расширяется так, чтобы разрешить создание списка базовых классов и обозначения их уровня доступа:

 
 
 
 
 
 
 
 
class X
{...};
class Y
{...};
class Z
{...};
class A : public X, public Y, public Z
{...};


Класс А обобщенно наследует элементы всех трех основных классов.
Множественное наследование

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

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

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

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

  • конструкторы базовых классов в порядке их задания;
  • конструкторы членов, являющихся объектами класса;
  • конструктор порожденного класса.

Деструкторы вызываются в порядке обратном вызову конструкторов.

Пример

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
using namespace std;
class X
{
protected:
  int key;
public:
  X(int i = 0) { cout << "X constructor" << endl; key = i; };
  ~X() { cout << "X destroyed" << endl; cin.get(); };
};
class Y 
{
protected:
  int key;
public:
  Y(int i = 0) { cout << "Y constructor" << endl; key = i; };
  ~Y() { cout << "Y destroyed" << endl; cin.get(); };
};
class Z 
{
protected:
  int key;
public:
  Z(int i = 0) { cout << "Z constructor" << endl; key = i; };
  ~Z() { cout << "Z destroyed" << endl; cin.get(); };
};
class A : public X, public Y, public Z 
{
  int key;
public:
  A(int i = 0) : X(i + 1), Y(i + 2), Z(i + 3)
  {
    key = X::key + Y::key + Z::key;
  }
  int getkey(void) { return(key); }
};
int main() 
{
  A object(4);
  cout << "object.key = " << object.getkey();
  cin.get();
  return 0;
}

Результат выполнения
Результат множественного наследования

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

Базовый класс может быть задан только один раз в списке порождения нового класса. Однако базовый класс может встретиться несколько раз в иерархии порождения.
Иерархия наследования
Такая иерархия порождения несет двусмысленность при доступе к наследуемым членам класса X и может привести к ошибкам. В этом случае класс X будет дважды присутствовать в A. Хорошо это или плохо - зависит от решаемой задачи.

Если двойное вхождение объектов класса X в объект класса А не является допустимым, существует два выхода для разрешения такой ситуации:

  • преобразование порождения из нескольких классов в порождение из одного класса и объявление дружественных классов.
     
     
    class Y : public X
      {friend class Z;  };   // класс Z имеет доступ к Y
  • изменение механизма наследования таким образом, чтобы вне зависимости от того, как часто базовый класс будет встречаться в иерархии порождения, генерировалась бы только одна его копия. Такой механизм заключается в возможности создания виртуальных базовых классов.

Базовый класс определяется как виртуальный заданием ключевого слова virtual в списке порождения перед именем базового класса или указанием типа наследования

 
 
 
 
сlass ИмяПорожденногоКласса : virtual МодификаторДоступа ИмяБазовогоКласса
{
// тело_класса
};

Если базовый класс объявляется как виртуальный при порождении нового класса, то каждый объект будет содержать свою собственную часть, а вместо базовой части будет содержать указатель на виртуальный базовый класс.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
using namespace std;
class X
{
protected:
  int key;
public:
  X(int i = 0) { cout << "X constructor" << endl; key = i; };
};

class Y : virtual public X
{
public:
  Y(int i = 0) { cout << "Y constructor" << endl; key = i; };
};

class Z : virtual public X
{
public:
  Z(int i = 0) { cout << "Y constructor" << endl; key = i; };
};

class A : public Y, public Z {
  int key;
public:
  A(int i = 0) : Y(i + 2), Z(i + 3)
  {
    key = Y::key + Z::key;
  }
  int getkey(void) { return(key); }
};
int main() {
  A object(4);
  cout << "object.key = " << object.getkey();
  cin.get();
  return 0;
}

Результат выполнения
Виртуальное наследование
Как видим, объект класса X был создан только один раз. Значение поля key изменяется каждый раз при доступе к нему как через класс Y, так и через класс Z.

Если в строках 11 и 17 этого примера убрать слово virtual, то результат работы программы изменится следующим образом:
Иерархия наследования
То есть в этом случае и объект класса Y, и объект класса Z, которые наследует объект класса A, будут содержать свою собственную копию объекта класса X.

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

  • конструкторы виртуальных базовых классов выполняются до конструкторов не виртуальных базовых классов, независимо от того, как эти классы заданы в списке порождения;
  • если класс имеет несколько виртуальных базовых классов, то конструкторы этих классов вызываются в порядке объявления виртуальных базовых классов в списке порождения;
  • деструкторы виртуальных базовых классов выполняются после деструкторов не виртуальных базовых классов.

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


Назад: Язык C++

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *