- •№1 Объекты и классы
- •Void main ( ) {/* ... */}
- •№5 Конструкторы класса
- •Конструкторы с аргументами
- •№7 Преобразование типов в производных классах
- •№8 Расширение характеристик при наследовании классов
- •Void Display ( ) ;
- •Void Display ( ) { }
- •Void Display ( ) ;
- •№9 Перегрузка и переопределение членов класса
- •№10 Использование виртуальных базовых классов
- •Int value;
№10 Использование виртуальных базовых классов
Виртуальные базовые классы используются только при множественном наследовании. Из-за сложности взаимосвязей, возникающих в дереве наследования, построенном на основе множественного наследования, возникают ситуации, когда программисту нужен определенный контроль над тем, как наследуются базовые классы. Рассмотрим дерево наследования, представленное на рис. 10.1.
Класс А является базовым для класса D. Проблема в том, что здесь два одинаковых класса А, которые являются базовыми для D, со своими собственными данными. В реальной жизни это подобно наличию у вас двух дедушек, которые не только близнецы, но и имеют одинаковые имена! Как быть? Рассмотрим следующий код:
Int value;
};
class В: public A { };
class C: public A { };
class D: public B, public С {
public:
int Value( ) { return value; } };
Рис. 10.1. Дерево наследования с классом, появляющимся дважды
Оператор доступа к члену данных value в D неоднозначен. Borland C++ сгенерирует ошибку:
(Поле 'value' является двусмысленным в функции D : : Valued)
Компилятор не понимает, на какую копию value ссылаться. Для устранения неоднозначности следует в функции D : : Value () применить оператор разрешения видимости: int Valued { return С : : value; }
Как показано в листинге 10.2, функция вне класса D также могла бы получить доступ к каждому value, используя, оператор разрешения видимости в сочетании с идентификатором объекта.
Листинг' 10.2. Использование оператора разрешения видимости в классе с несколькими базовыми классами
void main( )
{
D d;
int v = d.B : : value; // с объектом
D* object = new D;
int w = object->B : : value; // с указателем на объект
};
Наличие в дереве наследования нескольких копий одного и того же базового класса не только вносит путаницу, но и приводит к излишним затратам памяти. Объявление базового класса виртуальным решает эту проблему. Подобное объявление заставляет компилятор принимать только одну копию данного базового класса в объявлении производного класса. Тогда класс D можно определить следующим образом:
class В: public virtual A { };
class С: public virtual A { };
class D: public B, public С {
public:
int Valued { return value; }
};
Порядок ключевых слов public и virtual не имеет никакого значения. Дерево наследования, описываемое этим кодом, будет выглядеть так, как показано на рис. 10.3.
Рис. 10.3. Дерево наследования с классом, появляющимся дважды
Использование виртуальных и невиртуальных базовых классов вместе
Класс допускает существование как виртуальных, так и невиртуальных базовых классов. Они могут объявляться в любом порядке и в любой комбинации. Взаимосвязи между классами лучше видны из дерева наследования, представленного на рис. 10.4. Класс E наследует две разные копии класса А. Для компилятора C++ это не является ошибкой, но для пользователей может оказаться слишком сложным. Порядок вызова конструкторов для класса Е таков: А, В, С, D, E. Прежде всего, строятся виртуальные базы, а затем — все остальные. Порядок, по которому строятся виртуальные базовые классы, соответствует порядку их появления в объявлении класса. После вызова всех конструкторов виртуальных базовых классов вызываются конструкторы невиртуальных. И здесь порядок вызова зависит от порядка появления в объявлении.