Выбрать главу

Как видите, стрелки на этом рисунке направлены вверх, а не вниз. Многие поначалу считают такое направление стрелок алогичным, но именно этот стиль принят большинством С++-программистов. Согласно стилевой графике C++ стрелка должна указывать на базовый класс. Следовательно, стрелка означает "выведен из", а не "порождает". Рассмотрим другой пример. Можете ли вы описать словами значение следующего изображения?

Из этого графа следует, что класс Е выведен из обоих классов С и D. (Другими словами, класс Е имеет два базовых класса С и D.) При этом класс С выведен из класса А, а класс D — из класса В. Несмотря на то что направление стрелок может вас обескураживать на первых порах, все же лучше познакомиться с этим стилем графических обозначений, поскольку он широко используется в книгах, журналах и документации на компиляторы.

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

При наследовании нескольких базовых классов в С++-программу может быть внесен элемент неопределенности. Рассмотрим эту некорректную программу.

/* Эта программа содержит ошибку и не скомпилируется.

*/

#include <iostream>

using namespace std;

class base {

 public:

  int i;

};

// Класс derived1 наследует класс base.

class derived1 : public base { public: int j;};

// Класс derived2 наследует класс base.

class derived2 : public base { public: int k;};

/* Класс derived3 наследует оба класса derived1 и derived2. Это означает, что в классе derived3 существует две копии класса base!

*/

class derived3 : public derived1, public derived2 {

 public:

  int sum;

};

int main()

{

 derived3 ob;

 ob.i = 10; // Это и есть неоднозначность: какой именно член i имеется в виду???

 ob.j = 20;

 ob.k = 30;

 //И здесь тоже неоднозначность с членом i.

 ob.sum = ob.i + ob.j + ob.k;

 // И здесь тоже неоднозначность с членом i.

 cout << ob.i << " ";

 cout << ob. j << " " << ob.k << " ";

 cout << ob.sum;

 return 0;

}

Как отмечено в комментариях этой программы, оба класса derived1 и derived2 наследуют класс base. Но класс derived3 наследует как класс derived1, так и класс  derived2. В результате в объекте типа derived3 присутствуют две копии класса base, поэтому, например, в таком выражении

ob.i = 20;

не ясно, на какую именно копию члена i здесь дана ссылка: на член, унаследованный от класса derived1 или от класса derived2? Поскольку в объекте ob присутствуют обе копии класса base, то в нем существуют и два члена ob.is! Потому-то эта инструкция и является наследственно неоднозначной (существенно неопределенной).

Есть два способа исправить предыдущую программу. Первый состоит в применении оператора разрешения контекста (разрешения области видимости), с помощью которого можно "вручную" указать нужный член i. Например, следующая версия этой программы успешно скомпилируется и выполнится ожидаемым образом.

/* Эта программа использует оператор разрешения контекста для выбора нужного члена i.

*/

#include <iostream>

using namespace std;

class base {

 public:

  int i;

};

// Класс derived1 наследует класс base.

class derived1 : public base { public: int j;};

// Класс derived2 наследует класс base.

class derived2 : public base { public: int k;};