Виртуальные базовые классы
В сложной иерархии классов при множественном наследовании может получиться так, что производный класс косвенно унаследует два или более экземпляра одного и того же класса. Рассмотрим пример, который проясняет эту ситуацию. class Ground // базовый класс { int x; public: int GetX () { return x; } void SetX (int X) { x = X; } } class Basel: public Ground // наследует базовый класс Ground { • • • class Base2: public Ground // наследует базовый класс Ground { • • • class Derived: public Base1, public Base2 // производный класс – наследник классов Base1 и Base2 { • • • void main () { Derived ob; // создание объекта производного класса ob.SetX (1); int z = A.GetX (); } Здесь класс Derived косвенно наследует класс Ground через свои базовые классы Base1 и Base2. Поэтому при компиляции приведенного примера возникнут ошибки, вызванные неоднозначностью обращения к членам класса GetX () в строках: ob.SetX (0); int z = A.GetX (); Чтобы избежать этой неоднозначности, можно использовать квалификацию имен, применив операцию разрешения видимости: ob.Base1:: SetX (1); int z = A.Base1:: GetX (); Можно также квалифицировать эти вызовы следующим образом: ob.Base2:: SetX (1); { int x; public: int GetX () { return x; } void SetX (int X) { x = X; } } Объектно-ориентированное программирование Лекция 6 Множественное наследование class Basel: virtual public Ground // наследует класс Ground как виртуальный класс { • • • class Base2: virtual public Ground // наследует класс Ground как виртуальный класс { • • • class Derived: public Base1, public Base2 // производный класс – наследник классов Base1 и Base2 { • • • void main () { Derived ob; // создание объекта производного класса ob.SetX (1); int z = A.GetX (); } В этом случае класс Derived содержит один экземпляр класса Ground, и вызовы ob.SetX (1); int z = A.GetX (); не приводят к появлению сообщений компилятора об ошибках, связанных с неоднозначностью. Отсюда следует общая рекомендация: если разрабатывается иерархия классов и данный класс наследуется несколькими классами, включите перед спецификаторами наследуемого доступа ключевое слово virtual, то есть наследуйте его как виртуальный базовый класс. Наконец, отметим один важный момент. В случае, если класс имеет один или несколько базовых классов, их конструкторы вызываются перед вызовом конструктора производного класса. Конструкторы базовых классов вызываются в том порядке, в котором они объявлены в списке наследования. Объявление базового класса виртуальным изменяет порядок вызова конструкторов при создании экземпляра производного класса. Конструкторы виртуальных базовых классов вызываются первыми, раньше конструкторов невиртуатьных базовых классов. Если виртуальных базовых классов несколько, их конструкторы вызываются в порядке их объявления в списке наследования. Затем вызываются конструкторы невиртуальных базовых классов в порядке их объявления в списке наследования и, наконец, вызывается конструктор производного класса. Если какой-то виртуальный класс является производным невиртуального базового класса, этот невиртуальный базовый класс конструируется первым (иначе нельзя будет вызвать конструктор виртуального базового класса). Если иерархия классов содержит несколько экземпляров виртуального базового класса, этот базовый класс конструируется только один раз. Если все же существуют как виртуальный, так и невиртуальный экземпляры этого базового класса, конструктор базового класса вызывается один раз для всех виртуальных экземпляров этого базового класса, а затем еще раз для каждого невиртуального экземпляра этого базового класса. Деструкторы вызываются в порядке, в точности обратном конструкторам. Объектно-ориентированное программирование
|