Как видите, стрелки на этом рисунке направлены вверх, а не вниз. Многие поначалу считают такое направление стрелок алогичным, но именно этот стиль принят большинством С++-программистов. Согласно стилевой графике 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;};