Множественное наследование: объявление, примеры реализации, недостатки. Виртуальное наследование.
Как уже отмечалось, в С++ производный класс может быть порождён из любого числа непосредственных базовых классов. Наличие у производного класса более чем одного непосредственного базового класса называется множественным наследием. Синтаксически множественное наследование отличается от единичного наследования списком порождения, состоящим более чем из одного класса.
//Листинг 7. Пример множественного наследования
class A
{int a1;
public:
int a2;
void funcA()
};
class B
{int b1;
public:
int b2;
void funcB()
};
class C: public A, public B //наследуем класс С от A и B
{int c1;
public:
int c2;
void funcC()
};
Схема иерархии классов, определенных в последнем примере, изображена на рис.4
При множественном наследовании один и тот же класс не может быть дважды указан как прямой базовый, однако, косвенным базовым классом один и тот же класс может быть и более одного раза.
class A {public: int x; void funcA(); …};
class B: public A {…};
class D: public A{…};
class C: public B, public D {…};
Дублирование косвенного базового класса (рис 5а) приводит к включению в производный класс нескольких объектов базового класса. Для класса С в последнем примере это означает, что компонентное данное x будет существовать в объектах данного класса в двух экземплярах – один унаследован через класс В, другой – через класс D, что порождает проблему неоднозначности при доступе к дублирующимся компонентам класса: неясно, какой из одноименных компонент изменится при следующем обращении
main()
{ C c;
c.x=6; // Ошибка!!!
}
Попытка доступа к члену данных x для объекта с приводит к ошибке транслятора “Member is ambiguous A::x and A::x”. Эта ошибка означает, что транслятор не может определить, какому из двух компонент x класса необходимо присвоить новое значение. Неразрешимыми именами для транслятора будут также следующие с.C::x и c.A::x. Решением проблемы является использование квалифицированных имен компонент с использованием имен классов B и D. Для транслятора однозначно различаются следующие имена компонент: с.B::x (компонента, унаследованная через класс В) и c.D::x (компонента, унаследованная через класс D). Именно из-за сложности управления одноименными унаследованными компонентами класса множественное наследование реализаций было запрещено в языках программирования, появившихся после С++ ( например, в C# и Java).
Еще один вариант множественного наследования классов в языке С++ - использование виртуальных базовых классов. Если компоненты косвенного базового класса не должны дублироваться в классе-потомке, то он объявляется виртуальным:
class D {…};
class A: public virtual D {…};
class B: public virtual D {…};
class C: public A, public B{…};
Диаграмма классов в этом случае будет выглядеть как на рис. 5б. Компоненты косвенного базового класса присутствуют в классе С в единственном экземпляре, проблемы неоднозначности доступа к ним не возникает.