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 { ... };