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

Материал из Seo Wiki - Поисковая Оптимизация и Программирование

Перейти к: навигация, поиск
Про наследование виртуальных методов, см виртуальный метод.

В языке программирования C++, виртуальное наследование (англ. virtual inheritance) это один из вариантов наследования, который нужен для решения некоторых проблем, порождаемых наличием возможности множественного наследования (особенно «проблемой ромба» («diamond problem»)), путем разрешения неоднозначности того, методы которого из классов-предков необходимо использовать. Оно применяется в тех случаях, когда множественное наследование вместо предполагаемой полной композиции свойств классов-предков приводит к ограничению доступных наследуемых свойств вследствие неоднозначности. Базовый класс, наследуемый множественно, определяется виртуальным с помощью ключевого слова virtual.

Содержание

Суть проблемы

Рассмотрим следующую иерархию классов.

class Animal 
{
 public:
  virtual void eat();
};
 
class Mammal : public Animal 
{
 public:
  virtual Color getHairColor();
};
 
class WingedAnimal : public Animal 
{
 public:
  virtual void flap();
};
 
// A bat is a winged mammal
class Bat : public Mammal, public WingedAnimal {};
 
Bat bat;

Но как летучая мышь кушает? Что происходит при вызове метода bat.eat()?


В вышеприведенном коде вызов bat.eat() является неоднозначным. Он может относиться как к Bat::WingedAnimal::Animal::eat() так и к Bat::Mammal::Animal::eat(). Проблема в том, что семантика традиционного множественного наследования не соответствует моделируемой им реальности. В некотором смысле, сущность Animal единственна по сути; Bat это Mammal и WingedAnimal, но свойство животности (Animalness) летучей мыши (Bat), оно же свойство животности млекопитающего (Mammal) и оно же свойство животности WingedAnimal — по сути это одно и то же свойство.

Такая ситуация обычно именуется diamond inheritance («ромбическое наследование») и представляет из себя проблему, которую призвано решить виртуальное наследование.

Представление класса

Прежде чем продолжить, полезным будет рассмотреть как классы представляются в C++. В частности, при наследовании классы предка и наследника просто помещаются в памяти друг за другом. Таким образом объект класса Bat это на самом деле последовательность объектов классов (Animal,Mammal,Animal,WingedAnimal,Bat), размещенных последовательно в памяти, при этом Animal повторяется дважды, что и приводит к неоднозначности.

Решение

Мы можем переопределить наши классы следующим образом:

// Two classes virtually inheriting Animal:
class Mammal : public virtual Animal                // <--- обратите внимание на ключевое слово virtual
{
 public:
  virtual Color getHairColor();
};
 
class WingedAnimal : public virtual Animal          // <--- обратите внимание на ключевое слово virtual
{
 public:
  virtual void flap();
};
 
// A bat is still a winged mammal
class Bat : public Mammal, public WingedAnimal {};

Теперь часть Animal объекта класса Bat::WingedAnimal та же самая что и часть Animal, которая используется в Bat::Mammal, и можно сказать, что Bat имеет в своем представлении только одну часть Animal и вызов Bat::eat() становится однозначным.

Виртуальное наследование реализуется через добавление указателей на виртуальную таблицу vtable в классы Mammal и WingedAnimal, это делается в частности потому, что смещение памяти между началом Mammal и его Animal части неизвестно на этапе компиляции, а выясняется только во время выполнения. Таким образом Bat представляется как (vtable*, Mammal, vtable*, WingedAnimal, Bat, Animal). Два указателя vtable на объект увеличивают размер объекта на величину двух указателей, но это обеспечивает единственность Animal и отсутствие многозначности. Нужны два указателя vtables: по одному на каждого предка в иерархии, который виртуально наследуется от Animal: один для Mammal и один для WingedAnimal. Все объекты класса Bat будут иметь одни и те же указатели vtable *, но каждый отдельный объект Bat будет содержать собственную реализацию объекта Animal. Если какой-нибудь другой класс будет наследоваться от Mammal, например Squirrel, то vtable* в объекте Mammal объекта Squirrel будет отличаться от vtable* в объекте Mammal объекта Bat, хотя в особом случае они по-прежнему могут быть одинаковы по сути: когда часть Squirrel объекта имеет тот же самый размер, что и часть Bat, поскольку тогда расстояние от реализации Mammal до части Animal будет одинаковым. Но сами виртуальные таблицы vtables будут все же разными, в отличие от располагаемой в них информации о смещениях.

См. также

es:Herencia virtual ja:仮想継承

Личные инструменты

Served in 0.078 secs.