cout << ying_yang; //
Поскольку виртуальному базовому классу соответствует только один совместно используемый внутренний объект, к членам объекта этого базового класса можно обратиться непосредственно и однозначно. Кроме того, если член виртуального базового класса переопределяется только в одной ветви наследования, к этому переопределенному члену класса можно обратиться непосредственно. Если член переопределяется больше чем одним базовым классом, то производный класс вообще должен определить собственную версию этого члена.
Предположим, например, что класс В
определяет члены по имени x
; класс D1
виртуально наследует класс В
, как и класс D2
; а класс D
происходит от классов D1
и D2
. Из области видимости класса D
член x
видим через оба своих базовых класса. Есть три возможности использовать член x
через объект класса D
:
• Если член x
не будет определен ни в классе D1
, ни в D2
, то будет использован член класса В
; никакой неоднозначности нет. Объект класса D
содержит только один экземпляр члена x
.
• Если x
является членом класса В
и одного (но не обоих) из классов D1
или D2
, никакой неоднозначности снова нет: версия в производном классе имеет приоритет перед совместно используемым виртуальным базовым классом B
.
• Если член x
определяется и в классе D1
, и в классе D2
, то прямой доступ к этому члену неоднозначен.
Как и в иерархии с невиртуальным множественным наследованием, подобная неоднозначность лучше всего устраняется переопределением члена в производном классе.
Упражнение 18.28. Рассмотрим следующую иерархию класса. Можно ли в классе vmi
обращаться к унаследованным членам без уточнения? Какие из них требуют полностью квалифицированных имен? Объясните, почему.
struct Base {
void bar(int); //
protected:
int ival;
};
struct Derived1 : virtual public Base {
void bar(char); //
void foo(char);
protected:
char cval;
};
struct Derived2 : virtual public Base {
void foo(int); //
protected:
int ival;
char cval;
};
class VMI : public Derived1, public Derived2 { };
18.3.5. Конструкторы и виртуальное наследование
При виртуальном наследовании виртуальный базовый класс инициализируется Panda
инициализацию членов базового класса ZooAnimal
контролирует конструктор класса Panda.
Чтобы понять это правило, рассмотрим происходящее при применении обычных правил инициализации. В этом случае объект виртуального базового класса мог бы быть инициализирован несколько раз. Он был бы инициализирован вдоль каждой ветви наследования, содержащей этот виртуальный базовый класс. В данном примере, если бы к классу ZooAnimal
применялись обычные правила инициализации, то части Bear
и Raccoon
инициализировали бы часть ZooAnimal
объекта класса Panda
.
Конечно, каждый базовый класс в иерархии объекта мог бы в некоторый момент быть "более производным". Поскольку вполне можно создавать независимые объекты класса, производного от виртуального базового класса, конструкторы в этом классе должны инициализировать его виртуальный базовый класс. Например, когда в рассматриваемой иерархии создается объект класса Bear
(или Raccoon
), никакого дальнейшего применения производного класса нет. В данном случае конструкторы класса Bear
(или Raccoon
) непосредственно инициализируют базовую часть ZooAnimal
, как обычно:
Bear::Bear(std::string name, bool onExhibit) :
ZooAnimal(name, onExhibit, "Bear") { }
Raccoon::Raccoon(std::string name, bool onExhibit) :
ZooAnimal(name, onExhibit, "Raccoon") { }
Когда создается объект класса Panda, он является наиболее производным типом и контролирует инициализацию совместно используемого базового класса ZooAnimal
. Даже при том, что класс ZooAnimal
не является прямым базовым классом для класса Panda
, часть ZooAnimal
инициализирует конструктор класса Panda
:
Panda::Panda(std::string name, bool onExhibit)
: ZooAnimal(name, onExhibit, "Panda"),
Bear(name, onExhibit),
Raccoon(name, onExhibit),