18.5.4. Видимость членов виртуального базового класса
18.5.4. Видимость членов виртуального базового класса
Изменим наш класс Bear так, чтобы он имел собственную реализацию функции-члена onExhibit(), предоставляемой также ZooAnimal: bool Bear::onExhibit() { ... }
Теперь обращение к onExhibit() через объект Bear разрешается в пользу экземпляра, определенного в этом классе:
Bear winnie( "любитель меда" );
winnie.onExhibit(); // Bear::onExhibit()
Обращение же к onExhibit() через объект Raccoon разрешается в пользу функции-члена, унаследованной из ZooAnimal:
Raccoon meeko( "любитель всякой еды" );
meeko.onExhibit(); // ZooAnimal::onExhibit()
* Производный класс Panda наследует члены своих базовых классов. Их можно отнести к одной из трех категорий: члены виртуального базового класса ZooAnimal, такие, как name() и family(), не замещенные ни в Bear, ни в Raccoon;
* член onExhibit() виртуального базового класса ZooAnimal, наследуемый при обращении через Raccoon и замещенный в классе Bear;
* специализированные в классах Bear и Raccoon экземпляры функции print() из ZooAnimal.
Можно ли, не опасаясь неоднозначности, напрямую обращаться к унаследованным членам из области видимости класса Panda? В случае невиртуального наследования – нет: все неквалифицированные ссылки на имя неоднозначны. Что касается виртуального наследования, то прямое обращение допустимо к любым членам из первой и второй категорий. Например, дан объект класса Panda:
Panda spot( "Spottie" );
Тогда инструкция
spot.name();
вызывает разделяемую функцию-член name() виртуального базового ZooAnimal, а инструкция
spot.onExhibit();
вызывает функцию-член onExhibit() производного класса Bear.
Когда два или более экземпляров члена наследуются разными путями (это относится не только к функциям-членам, но и к данным-членам, а также к вложенным типам) и все они представляют один и тот же член виртуального базового класса, неоднозначности не возникает, поскольку существует единственный разделяемый экземпляр (первая категория). Если один экземпляр представляет член виртуального базового, а другой – член унаследованного от него класса, то неоднозначности также не возникает: специализированному экземпляру из производного класса отдается предпочтение по сравнению с разделяемым экземпляром из виртуального базового (вторая категория). Но если оба экземпляра представляют члены производных классов, то прямое обращение неоднозначно. Лучше всего разрешить эту ситуацию, предоставив замещающий экземпляр в производном классе (третья категория).
Например, при невиртуальном наследовании неквалифицированное обращение к onExhibit() через объект Panda неоднозначно:
// ошибка: неоднозначно при невиртуальном наследовании
Panda yolo( "любитель бамбука" );
yolo.onExhibit();
В данном случае все унаследованные экземпляры имеют равные приоритеты при разрешении имени, поэтому неквалифицированное обращение приводит к ошибке компиляции из-за неоднозначности (см. раздел 18.4.1).
При виртуальном наследовании члену, унаследованному из виртуального базового класса, приписывается меньший приоритет, чем члену с тем же именем, замещенному в производном. Так, унаследованному от Bear экземпляру onExhibit() отдается предпочтение перед экземпляром из ZooAnimal, унаследованному через Raccoon:
// правильно: при виртуальном наследовании неоднозначности нет
// вызывается Bear::onExhibit()
yolo.onExhibit();
Если два или более классов на одном и том же уровне наследования замещают некоторый член виртуального базового, то в производном они будут иметь одинаковый вес. Например, если в Raccoon также определен член onExhibit(), то при обращении к нему из Panda придется квалифицировать имя с помощью оператора разрешения области видимости:
bool Panda::onExhibit()
{
return Bear::onExhibit() &&
Raccoon::onExhibit() &&
! _sleeping;
}
Упражнение 18.13
Дана иерархия классов:
class Class { ... };
class Base : public Class { ... };
class Derived1 : virtual public Base { ... };
class Derived2 : virtual public Base { ... };
class MI : public Derived1,
public Derived2 { ... };
class Final : public MI, public Class { ... };
(a)В каком порядке вызываются конструкторы и деструкторы при определении объекта Final?
(b)Сколько подобъектов класса Base содержит объект Final? А сколько подобъектов Class?
(c)Какие из следующих присваиваний вызывают ошибку компиляции?
Base *pb;
MI *pmi;
Class *pc;
Derived2 *pd2;
(i) pb = new Class; (iii) pmi = pb;
(ii) pc = new Final; (iv) pd2 = pmi;
Упражнение 18.14
Дана иерархия классов:
class Base {
public:
bar( int );
// ...
protected:
int ival;
// ...
};
class Derived1 : virtual public Base {
public:
bar( char );
foo( char );
// ...
protected:
char cval;
// ...
};
class Derived2 : virtual public Base {
public:
foo( int );
// ...
protected:
int ival;
char cval;
// ...
};
class VMI : public Derived1, public Derived2 {};
К каким из унаследованных членов можно обращаться из класса VMI, не квалифицируя имя? А какие требуют квалификации?
Упражнение 18.15
Дан класс Base с тремя конструкторами:
class Base {
public:
Base();
Base( string );
Base( const Base& );
// ...
protected:
string _name;
};
Определите соответствующие конструкторы для каждого из следующих классов:
(a) любой из
class Derived1 : virtual public Vase { ... };
class Derived2 : virtual public Vase { ... };
(b) class VMI : public Derived1, public Derived2 { ... };
(c) class Final : public VMI { ... };