По умолчанию объект производного класса содержит отдельные части, соответствующие каждому классу в его цепи наследования. Если тот же базовый класс наследуется несколько раз, то у объекта производного класса будет больше одного внутреннего объекта этого типа.
Для такого класса, как iostream
, это стандартное поведение не работает. Объект класса iostream
должен использовать тот же буфер и для чтения, и для записи, а его флаг должен отражать состояние операций и ввода, и вывода. Если у объекта класса iostream
будут две копии объекта класса basic_ios
, то их совместное использование невозможно.
В языке С++ для решения этой проблемы используется
Panda
В прошлом велись дебаты о принадлежности вида панда к семейству енотов или медведей. Чтобы отобразить эти сомнения, изменим класс Panda
так, чтобы он происходил и от класса Bear
, и от класса Raccoon
. Чтобы избавить класс Panda
от двух частей базового класса ZooAnimal
, определим наследование классов Bear
и Raccoon
от класса ZooAnimal
как виртуальное. Новая иерархия представлена на рис. 18.3.
Рис. 18.3. Виртуальное наследование в иерархии класса Panda
Глядя на новую иерархию, можно заметить неочевидный аспект виртуального наследования. Виртуальное наследование должно быть осуществлено прежде, чем в нем возникнет потребность. Например, в этих классах потребность в виртуальном наследовании возникает только при определении класса Panda
. Но если бы классы Bear
и Raccoon
не определили бы свое происхождение от класса ZooAnimal
как виртуальное, конструкция класса Panda
была бы неудачна.
На практике необходимость наличия промежуточного базового класса при виртуальном наследовании редко создает проблемы. Обычно иерархия классов, в которой используется виртуальное наследование, разрабатывается сразу и одним лицом (или группой разработчиков). Ситуации, когда разработку виртуального базового класса необходимо поручить независимому производителю, чрезвычайно редки, а разработчик нового базового класса не может внести изменения в существующую иерархию.
Базовый класс объявляется виртуальным при помощи ключевого слова 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); //
rummage(ying_yang); //