Специальная семантика инициализации
Наследование, в котором присутствует один или несколько виртуальных базовых классов, требует специальной семантики инициализации. Взгляните еще раз на реализации Bear и Raccoon в предыдущем разделе. Видите ли вы, какая проблема связана с порождением класса Panda?
class Panda : public Bear,
public Raccoon, public Endangered {
public:
Panda( string name, bool onExhibit=true );
virtual ostream& print( ostream& ) const;
bool sleeping() const { return _sleeping; }
void sleeping( bool newval ) { _sleeping = newval; }
// ...
protected:
bool _sleeping;
// ...
};
Проблема в том, что конструкторы базовых классов Bear и Raccoon вызывают конструктор ZooAnimal с неявным набором аргументов. Хуже того, в нашем примере значения по умолчанию для аргумента fam_name (название семейства) не только отличаются, они еще и неверны для Panda.
В случае невиртуального наследования производный класс способен явно инициализировать только свои непосредственные базовые классы (см. раздел 17.4). Так, классу Panda, наследующему от ZooAnimal, не разрешается напрямую вызвать конструктор ZooAnimal в своем списке инициализации членов. Однако при виртуальном наследовании только Panda может напрямую вызывать конструктор своего виртуального базового класса ZooAnimal.
Ответственность за инициализацию виртуального базового возлагается на ближайший производный класс. Например, когда объявляется объект класса Bear:
Bear winnie( "pooh" );
то Bear является ближайшим производным классом для объекта winnie, поэтому выполняется вызов конструктора ZooAnimal, определенный в классе Bear. Когда мы пишем:
cout << winnie.family_name();
будет выведена строка:
The family name for pooh is Bear
(Название семейства для pooh – это Bear)
Аналогично для объявления
Raccoon meeko( "meeko" );
Raccoon – это ближайший производный класс для объекта meeko, поэтому выполняется вызов конструктора ZooAnimal, определенный в классе Raccoon. Когда мы пишем:
cout << meeko.family_name();
печатается строка:
The family name for meeko is Raccoon
(Название семейства для meeko - это Raccoon)
Если же объявить объект типа Panda:
Panda yolo( "yolo" );
то ближайшим производным классом для объекта yolo будет Panda, поэтому он и отвечает за инициализацию ZooAnimal.
Когда инициализируется объект Panda, то явные вызовы конструктора ZooAnimal в конструкторах классов Raccoon и Bear не выполняются, а вызывается он с теми аргументами, которые указаны в списке инициализации членов объекта Panda. Вот так выглядит реализация:
Panda::Panda( string name, bool onExhibit=true )
: ZooAnimal( name, onExhibit, "Panda" ),
Bear( name, onExhibit ),
Raccoon( name, onExhibit ),
Endangered( Endangered::environment,
Endangered::critical ),
sleeping( false )
{}
Если в конструкторе Panda аргументы для конструктора ZooAnimal не указаны явно, то вызывается конструктор ZooAnimal по умолчанию либо, если такового нет, выдается ошибка при компиляции определения конструктора Panda.
Когда мы пишем:
cout << yolo.family_name();
печатается строка:
The family name for yolo is Panda
(Название семейства для yolo - это Panda)
Внутри определения Panda классы Raccoon и Bear являются промежуточными, а не ближайшими производными. В промежуточном производном классе все прямые вызовы конструкторов виртуальных базовых классов автоматически подавляются. Если бы от Panda был в дальнейшем произведен еще один класс, то сам класс Panda стал бы промежуточным и вызов из него конструктора ZooAnimal также был бы подавлен.
Обратите внимание, что оба аргумента, передаваемые конструкторам Bear и Raccoon, излишни в том случае, когда они выступают в роли промежуточных производных классов. Чтобы избежать передачи ненужных аргументов, мы можем предоставить явный конструктор, вызываемый, когда класс оказывается промежуточным производным. Изменим наш конструктор Bear:
class Bear : public virtual ZooAnimal {
public:
// если выступает в роли ближайшего производного класса
Bear( string name, bool onExhibit=true )
: ZooAnimal( name, onExhibit, "Bear" ),
_dance( two_left_feet )
{}
// ... остальное без изменения
protected:
// если выступает в роли промежуточного производного класса
Bear() : _dance( two_left_feet ) {}
// ... остальное без изменения
};
Мы сделали этот конструктор защищенным, поскольку он вызывается только из производных классов. Если аналогичный конструктор по умолчанию обеспечен и для класса Raccoon, можно следующим образом модифицировать конструктор Panda:
Panda::Panda( string name, bool onExhibit=true )
: ZooAnimal( name, onExhibit, "Panda" ),
Endangered( Endangered::environment,
Endangered::critical ),
sleeping( false )
{}