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

Для такого класса, как iostream, это стандартное поведение не работает. Объект класса iostream должен использовать тот же буфер и для чтения, и для записи, а его флаг должен отражать состояние операций и ввода, и вывода. Если у объекта класса iostream будут две копии объекта класса basic_ios, то их совместное использование невозможно.

В языке С++ для решения этой проблемы используется виртуальное наследование (virtual inheritance). Виртуальное наследование позволяет классу указать, что его базовый класс будет использоваться совместно. Совместно используемый внутренний объект базового класса называется виртуальным базовым классом (virtual base class). Независимо от того, сколько раз тот же базовый виртуальный класс присутствует в иерархии наследования, объект производного класса содержит только один совместно используемый внутренний объект этого виртуального базового класса.

Разные классы Panda

В прошлом велись дебаты о принадлежности вида панда к семейству енотов или медведей. Чтобы отобразить эти сомнения, изменим класс Panda так, чтобы он происходил и от класса Bear, и от класса Raccoon. Чтобы избавить класс Panda от двух частей базового класса ZooAnimal, определим наследование классов Bear и Raccoon от класса ZooAnimal как виртуальное. Новая иерархия представлена на рис. 18.3.

Рис. 18.3. Виртуальное наследование в иерархии класса Panda

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

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

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

Использование виртуального базового класса

Базовый класс объявляется виртуальным при помощи ключевого слова virtual в списке наследования:

// порядок расположения ключевых слов public и virtual несуществен

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

class Bear : virtual public ZooAnimal { /* ... */ };

Здесь класс ZooAnimal объявлен виртуальным базовым для классов Bear и Raccoon.

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

Для наследования от класса, имеющего виртуальный базовый класс, не нужно ничего особенного:

class Panda : public Bear,

              public Raccoon, public Endangered {

};

Здесь класс Panda наследует класс ZooAnimal через два своих базовых класса — Raccoon и Bear. Но поскольку эти классы происходят от класса ZooAnimal виртуально, у класса Panda есть только одна часть базового класса ZooAnimal.

Для базовых классов поддерживаются стандартные преобразования

Объектом производного класса можно манипулировать как обычно, при помощи указателя или ссылки на базовый класс, хотя он и является виртуальным. Например, все следующие преобразования для базового класса объекта класса Panda вполне допустимы:

void dance(const Bear&);

void rummage(const Raccoon&);

ostream& operator<<(ostream&, const ZooAnimal&);

Panda ying_yang;

dance(ying_yang);   // ok: передает объект Panda как Bear

rummage(ying_yang); // ok: передает объект Panda как Raccoon

Перейти на страницу:

Похожие книги