Сделай Сам Свою Работу на 5

Объявление виртуального базового класса

Для указания виртуального наследования в объявление базового класса вставляется модификатор virtual. Так, в данном примере ZooAnimal становится виртуальным базовым для Bear и Raccoon:

// взаимное расположение ключевых слов public и virtual // несущественно   class Bear : public virtual ZooAnimal { ... };

class Raccoon : virtual public ZooAnimal { ... };

Виртуальное наследование не является явной характеристикой самого базового класса, а лишь описывает его отношение к производному. Как мы уже отмечали, виртуальное наследование – это разновидность композиции по ссылке. Иначе говоря, доступ к подобъекту и его нестатическим членам косвенный, что обеспечивает гибкость, необходимую для объединения нескольких виртуально унаследованных подобъектов базовых классов в один разделяемый экземпляр внутри производного. В то же время объектом производного класса можно манипулировать через указатель или ссылку на тип базового, хотя последний является виртуальным. Например, все показанные ниже преобразования базовых классов Panda выполняются корректно, хотя Panda использует виртуальное наследование:

extern void dance( const Bear* ); extern void rummage( const Raccoon* );   extern ostream& operator<<( ostream&, const ZooAnimal& );   int main() { Panda yin_yang;   dance( &yin_yang ); // правильно rummage( &yin_yang ); // правильно cout << yin_yang; // правильно // ...

}

Любой класс, который можно задать в качестве базового, разрешается сделать виртуальным, причем он способен содержать все те же элементы, что обычные базовые классы. Так выглядит объявление ZooAnimal:

#include <iostream> #include <string>   class ZooAnimal; extern ostream& operator<<( ostream&, const ZooAnimal& ); class ZooAnimal { public: ZooAnimal( string name, bool onExhibit, string fam_name ) : _name( name ), _onExhibit( onExhibit ), _fam_name( fam_name ) {}   virtual ~ZooAnimal(); virtual ostream& print( ostream& ) const; string name() const { return _name; } string family_name() const { return _fam_name; } // ...   protected: bool _onExhibit; string _name; string _fam_name; // ...

};

К объявлению и реализации непосредственного базового класса при использовании виртуального наследования добавляется ключевое слово virtual. Вот, например, объявление нашего класса Bear:



class Bear : public virtual ZooAnimal { public: enum DanceType { two_left_feet, macarena, fandango, waltz };   Bear( string name, bool onExhibit=true ) : ZooAnimal( name, onExhibit, "Bear" ), _dance( two_left_feet ) {}   virtual ostream& print( ostream& ) const; void dance( DanceType ); // ...   protected: DanceType _dance; // ...

};

А вот объявление класса Raccoon:

class Raccoon : public virtual ZooAnimal { public: Raccoon( string name, bool onExhibit=true ) : ZooAnimal( name, onExhibit, "Raccoon" ), _pettable( false ) {}   virtual ostream& print( ostream& ) const;   bool pettable() const { return _pettable; } void pettable( bool petval ) { _pettable = petval; } // ...   protected: bool _pettable; // ...

};

Специальная семантика инициализации

Наследование, в котором присутствует один или несколько виртуальных базовых классов, требует специальной семантики инициализации. Взгляните еще раз на реализации 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 )

{}



©2015- 2019 stydopedia.ru Все материалы защищены законодательством РФ.