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),

   Endangered(Endangered::critical),

   sleeping_flag(false) { }

Как создается объект при виртуальном наследовании

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

Например, объект класса Panda создается так.

• Сначала создается часть виртуального базового класса ZooAnimal. При этом используются инициализаторы из списка инициализации конструктора класса Panda.

• Затем создается часть Bear.

• Затем создается часть Raccoon.

• Следующей создается часть прямого базового класса Endangered.

• Наконец создается часть Panda.

Если конструктор класса Panda не инициализирует явно часть базового класса ZooAnimal, будет использован стандартный конструктор класса ZooAnimal. Если у класса ZooAnimal нет стандартного конструктора, произойдет ошибка.

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

Порядок выполнения конструкторов и деструкторов

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

class Character { /* ... */ };

class BookCharacter : public Character { /* ... */ };

class ToyAnimal { /* ... */ };

class TeddyBear : public BookCharacter,

 public Bear, public virtual ToyAnimal

 { / * ... * / };

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

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

ToyAnimal();     // прямой виртуальный базовый класс

Character();     // косвенный базовый класс первого не виртуального

                 // базового класса

BookCharacter(); // первый прямой не виртуальный базовый класс

Bear();          // второй прямой не виртуальный базовый класс

TeddyBear();     // наиболее производный класс

Тот же порядок создания используется в синтезируемом конструкторе копий и конструкторах перемещения, в синтезируемых операторах присвоения члены присваиваются в том же порядке. Вызов деструкторов базовых классов осуществляется в порядке, обратном порядку вызова конструкторов. Часть TeddyBear будет удалена сначала, а часть ZooAnimal — последней.

Упражнения раздела 18.3.5

Упражнение 18.29. Имеется следующая иерархия классов:

class Class { ... };

class Base : public Class { ... };

class D1 : virtual public Base { ... };

class D2 : virtual public Base { ... };

class MI : public D1, public D2 { ... };

class Final : public MI, public Class { ... };

(a) Каков порядок вызова конструкторов и деструкторов объектов класса Final?

(b) Сколько внутренних объектов класса Base находится в объекте класса Final? А сколько внутренних объектов класса Class?

(c) Какие из следующих случаев присвоения приведут к ошибке во время компиляции?

Base *pb; Class *pc; MI *pmi; D2 *pd2;

(a) pb = new Class; (b) pc = new Final;

(c) pmi = pb;       (d) pd2 = pmi;

Упражнение 18.30. Определите в классе Base стандартный конструктор, конструктор копий и конструктор с параметром типа int. Определите те же три конструктора в каждом производном классе. Каждый конструктор должен использовать свой аргумент для инициализации своей части Base.

Более 800 000 книг и аудиокниг! 📚

Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением

ПОЛУЧИТЬ ПОДАРОК