Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
85
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

958

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; // ...

};

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;

//...

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

};

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

Наследование, в котором присутствует один или несколько виртуальных базовых классов, требует специальной семантики инициализации. Взгляните еще раз на реализации Bear и Raccoon в предыдущем разделе. Видите ли вы, какая проблема связана с порождением класса Panda?

С++ для начинающих

959

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();

С++ для начинающих

960

печатается строка:

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:

С++ для начинающих

961

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 ) {}

//... остальное без изменения

};

Мы сделали этот конструктор защищенным, поскольку он вызывается только из производных классов. Если аналогичный конструктор по умолчанию обеспечен и для

Panda::Panda( string name, bool onExhibit=true )

:ZooAnimal( name, onExhibit, "Panda" ), Endangered( Endangered::environment,

Endangered::critical ), sleeping( false )

класса Raccoon, можно следующим образом модифицировать конструктор Panda:

{}

18.5.3. Порядок вызова конструкторов и деструкторов

Виртуальные базовые классы всегда конструируются перед невиртуальными, вне зависимости от их расположения в иерархии наследования. Например, в приведенной иерархии у класса TeddyBear (плюшевый мишка) есть два виртуальных базовых: непосредственный ToyAnimal (игрушечное животное) и экземпляр ZooAnimal, от

class Character { ... };

// персонаж

class BookCharacter : public Character { ... };

class ToyAnimal { ... };

// литературный персонаж

// игрушка

class TeddyBear : public BookCharacter,

public Bear, public virtual ToyAnimal

которого унаследован класс Bear:

{ ... };

Эта иерархия изображена на рис. 18.5, где виртуальное наследование показано пунктирной стрелкой, а невиртуальное сплошной.

Character

ZooAnimal

ToyAnimal

С++ для начинающих

962

BookCharacter Bear

TeddyBear

¾¾> невиртуальное наследование

- - - -> виртуальноe наследование

Рис. 18.5. Иерархия виртуального наследования класса TeddyBear

Непосредственные базовые классы просматриваются в порядке их объявления при поиске среди них виртуальных. В нашем примере сначала анализируется поддерево наследования BookCharacter, затем Bear и наконец ToyAnimal. Каждое поддерево обходится в глубину, т.е. поиск начинается с корневого класса и продвигается вниз. Так, для поддерева BookCharacter сначала просматривается Character, а затем

BookCharacter. Для поддерева Bear ZooAnimal, а потом Bear.

При описанном алгоритме поиска порядок вызова конструкторов виртуальных базовых классов для TeddyBear таков: ZooAnimal, потом ToyAnimal.

После того как вызваны конструкторы виртуальных базовых классов , настает черед конструкторов невиртуальных, которые вызываются в порядке объявления: BookCharacter, затем Bear. Перед выполнением конструктора BookCharacter вызывается конструктор его базового класса Character.

Если имеется объявление:

TeddyBear Paddington;

ZooAnimal();

// виртуальный базовый класс Bear

ToyAnimal();

// непосредственный виртуальный базовый класс

Character();

// невиртуальный базовый класс BookCharacter

BookCharacter();

// непосредственный невиртуальный базовый класс

Bear();

// непосредственный невиртуальный базовый класс

то последовательность вызова конструкторов базовых классов будет такой:

TeddyBear();

// ближайший производный класс

причем за инициализацию ZooAnimal и ToyAnimal отвечает TeddyBear ближайший производный класс объекта Paddington.

Порядок вызова копирующих конструкторов при почленной инициализации (и копирующих операторов присваивания при почленном присваивании) такой же. Гарантируется, что деструкторы вызываются в последовательности, обратной вызову конструкторов.