- •Глава 12
- •Конструкторы и деструкторы. Список инициализации элементов
- •Конструкторы по умолчанию и конструкторы копирования
- •Указатель this
- •Встраиваемые (inline-) функции
- •Статические члены класса
- •Использование указателей на функции-члены класса
- •Массивы объектов класса
- •Дружественные функции и друзья класса
- •Глава 13
- •Множественное наследование
- •Виртуальные базовые классы
- •Глава 14
- •Перегрузка функций
- •Перегрузка конструкторов
- •Создание и использование конструкторов копирования
- •Определение адреса перегруженной функции
Виртуальные базовые классы
В сложной иерархии классов при множественном наследовании может получиться так, что производный класс косвенно унаследует два или более экземпляра одного и того же класса. Рассмотрим пример, который проясняет эту ситуацию.
class IndBase
{
private:
int x;
public:
int GetX(){return x;}
void SetX(int _x){x = _x;}
double var;
};
class Basel: public IndBase
{
//...
};
class Base2: public IndBase
{
//...
};
class Derived: public Basel, public Base2
{
//...
};
main()
{
Derived ob;
ob.var = 5.0;
ob.SetX(0);
int z = ob. GetX () ;
//ob.Basel::var = 5.0;
//ob.Basel::SetX(0) ;
//int z = ob.Basel::GetX ();
return 0;
}
Здесь класс Derived косвенно наследует класс IndBase через свои базовые классы Basel и Base2. Поэтому при компиляции приведенного примера возникнут ошибки, вызванные неоднозначностью обращения к членам класса var и GetX () в строках:
ob.var =5.0;
ob.SetX(0);
int z = ob.GetX() ;
Чтобы избежать этой неоднозначности, можно использовать квалификацию имен, применив операцию разрешения видимости:
ob.Basel::var = 5.0;
ob.Basel::SetX(0);
int z = ob.Basel::GetX();
Можно также квалифицировать эти вызовы следующим образом:
ob.Base2::var = 5.0;
ob.Base2::SetX(0);
int z = ob.Base2::GetX();
Хотя этот способ и позволяет избежать неоднозначности при вызове, тем не менее класс IndBase будет включен в состав класса Derived дважды, увеличивая его размер. Избежать повторного включения косвенного базового класса в производный класс можно, дав указание компилятору использовать виртуальный базовый класс. Это осуществляется с помощью ключевого слова virtual, которое указывается перед спецификатором наследуемого доступа или после него. Следующий пример является модифицированным вариантом предыдущего, использующим класс IndBase в качестве виртуального базового класса.
class IndBase
{
private:
int x;
public:
int GetX(){return x;}
void SetX(int _x){x = _x;} double var;
};
class Basel: virtual public IndBase
{
//...
};
class Base2: virtual public IndBase
{
//...
};
class Derived: public Basel, public Base2
{
//...
};
main()
{
Derived ob;
ob.var = 5.0;
ob.SetX(0);
int z = ob.GetX() ;
return 0;
}
В этом случае класс Derived содержит один экземпляр класса IndBase. и вызовы
ob.var = 5.0;
ob.SetX(0);
int z = ob.GetX() ;
не приводят к появлению сообщений компилятора об ошибках, связанных с неоднозначностью.
Отсюда следует общая рекомендация: если разрабатывается иерархия классов и данный класс наследуется несколькими классами, включите перед спецификаторами наследуемого доступа ключевое слово virtual, то есть наследуйте его как виртуальный базовый класс.
Наконец, отметим один важный момент. В случае, если класс имеет один или несколько базовых классов, их конструкторы вызываются перед вызовом конструктора производного класса. Конструкторы базовых классов вызываются в том порядке, в котором они объявлены в списке наследования. Например, в случае объявления
class Basel{};
class Base2{};
class Derived: public Basel, public Base2 {};
main()
{
Derived ob;
//...
return 0;
}
конструкторы вызываются в следующем порядке:
Basel () ;
Base2() ;
Derived () ;
Объявление базового класса виртуальным изменяет порядок вызова конструкторов при создании экземпляра производного класса. Конструкторы виртуальных базовых классов вызываются первыми, раньше конструкторов невиртуальных базовых классов.
Если виртуальных базовых классов несколько, их конструкторы вызываются в порядке их объявления в списке наследования. Затем вызываются конструкторы невиртуальных базовых классов в порядке их объявления в списке наследования и, наконец, вызывается конструктор производного класса. Если какой-то виртуальный класс является производным невиртуального базового класса, этот невиртуальный базовый класс конструируется первым (иначе нельзя будет вызвать конструктор виртуального базового класса). Например, в случае объявления
class Basel{};
class Base2{};
class Derived: public Basel, virtual public Base2 { } ;
main()
{
Derived ob;
//…;
return 0;
}
конструкторы вызываются в следующем порядке:
Base2() ;
Basel () ;
Derived();
Если иерархия классов содержит несколько экземпляров виртуального базового класса, этот базовый класс конструируется только один раз. Если все же существуют как виртуальный, так и невиртуальный экземпляры этого базового класса, конструктор базового класса вызывается один раз для всех виртуальных экземпляров этого базового класса, а затем еще раз для каждого невиртуального экземпляра этого базового класса.
Деструкторы вызываются в порядке, в точности обратном конструкторам.
