Например, если классы ZooAnimal
и Endangered
определяют функцию-член max_weight()
, а класс Panda
не определяет ее, то следующий вызов ошибочен:
double d = ying_yang.max_weight();
В результате наследования класс Panda
получает две функции-члена max_weight()
, что совершенно допустимо. Наследование создает потенциальную неоднозначность. Ее вполне можно избежать, если объект Panda
не будет вызывать функцию-член max_weight()
. Ошибки также можно избежать, если явно указать требуемую версию функции: ZooAnimal::max_weight()
или Endangered::max_weight()
. Ошибка неоднозначности произойдет только при попытке использования функции без уточнения.
Неоднозначность двойного наследования функции-члена max_weight
вполне очевидна и логична. Удивительно узнать то, что ошибка произошла бы, даже если у двух наследованных функций были разные списки параметров. Точно так же эта ошибка произошла бы даже в случае, если бы функция max_weight()
была закрытой в одном классе и открытой или защищенной в другом. И наконец, если бы функция max_weight()
была определена в классе Bear
, а не в классе ZooAnimal
, то вызов все равно был бы ошибочен.
Как обычно, поиск имени осуществляется под контролем соответствия типов (см. раздел 6.4.1). Когда компилятор находит имя функции max_weight()
в двух разных областях видимости, он оповещает об ошибке неоднозначности.
Проще всего избежать потенциальных неоднозначностей, определив версию такой функции в производном классе. Например, снабдив класс Panda
функцией max_weight()
, можно решить все проблемы:
double Panda::max_weight() const {
return std::max(ZooAnimal::max_weight(),
Endangered::max_weight());
}
Упражнение 18.26. С учетом иерархии кода для упражнений объясните, почему ошибочен следующий вызов функции print()
? Исправьте структуру MI
так, чтобы позволить этот вызов.
MI mi;
mi.print(42);
Упражнение 18.27. С учетом иерархии кода для упражнений и того, что в структуру MI
добавлена приведенная ниже функция foo()
, ответьте на следующие вопросы:
int ival;
double dval;
void MI::foo(double cval) {
int dval;
//
}
(a) Перечислите все имена, видимые из функции MI::foo()
.
(b) Видимы ли какие-нибудь имена из больше чем одного базового класса?
(c) Присвойте локальному экземпляру переменной dval
сумму переменных-членов dval
объектов классов Base1
и Derived
.
(d) Присвойте значение последнего элемента вектора MI::dvec
переменной-члену Base2::fval
.
(e) Присвойте переменной-члену cval
класса Base1
первый символ строки sval
класса Derived
.
struct Base1 {
void print(int) const; //
protected:
int ival;
double dval;
char cval;
private:
int *id;
};
struct Base2 {
void print(double) const; //
protected:
double fval;
private:
double dval;
};
struct Derived : public Base1 {
void print(std::string) const; //
protected:
std::string sval;
double dval;
};
struct MI : public Derived, public Base2 {
void print(std::vector
protected:
int *ival;
std::vector
};
18.3.4. Виртуальное наследование
Хотя список наследования класса не может включать тот же базовый класс несколько раз, класс вполне может унаследовать тот же базовый класс многократно. Тот же базовый класс может быть унаследован косвенно, от двух его собственных прямых базовых классов, либо он может унаследовать некий класс и прямо, и косвенно, через другой из его базовых классов.
Например, библиотечные классы ввода-вывода istream
и ostream
происходят от общего абстрактного базового класса basic_ios
. Этот класс содержит буфер потока и управляет флагом состояния потока. Класс iostream
, способный и читать, и писать в поток, происходит непосредственно и от класса istream
, и от класса ostream
. Поскольку оба класса происходят от класса basic_ios
, класс iostream
наследует этот базовый класс дважды: один раз от класса istream
и один раз от класса ostream
.