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

Про наследование виртуальных методов, см виртуальный метод .

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

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

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

class Animal 
{
 public:
  virtual void eat();  // Метод определяется для данного класса
 ...
};

class Mammal : public Animal 
{
 public:
  Color getHairColor(); 
 ...
};

class WingedAnimal : public Animal 
{
 public:
  void flap();
 ...
};

// A bat is a winged mammal
class Bat : public Mammal, public WingedAnimal {};   //<--- обратите внимание, что метод eat() не переопределен в Bat

Bat bat;
Виртуальное наследование на примере базового класса Animal
Виртуальное наследование на примере базового класса Animal

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

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

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

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

Решение

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

class Animal 
{
 public:
  virtual void eat();
 ...
};

// Two classes virtually inheriting Animal:
class Mammal : public virtual Animal                // <--- обратите внимание на ключевое слово virtual
{
 public:
  Color getHairColor();
 ...
};

class WingedAnimal : public virtual Animal          // <--- обратите внимание на ключевое слово virtual
{
 public:
  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() становится однозначным.

Виртуальное наследование реализуется через добавление указателей в классы Mammal и WingedAnimal . Таким образом, Bat представляется, как (ptr, Mammal, ptr, WingedAnimal, Bat, Animal). *ptr содержит информацию о смещении в памяти между началом Mammal / WingedAnimal и его Animal . За счёт этого обеспечивается не только исключение дублирования общей Animal части для Mammal и WingedAnimal , но и простой механизм преобразования указателя (ссылки) на объект класса наследника в указатель (ссылку) на объект любого базового класса. Очевидно, что избыточное применение виртуального наследования даст некоторое снижение производительности (аналогичное таковому у виртуальных функций - за счёт дополнительной операции чтения).

Пример

Чтобы понять суть виртуального наследования без лишнего "шума", следует рассмотреть следующий пример:

#include <iostream>

class A {
public:
    virtual int foo() {
        return 1;
    }
};
 
class B : public virtual A {};
 
class C : public virtual A {};
 
class D : public B, public C {};

int main () {
    D d;
    std::cout << d.foo();
    return 0;
}

Если убрать ключевое слово virtual , то метод foo() не может быть определён однозначно и в результате не будет доступен, как и объект класса D — код не скомпилируется.

См. также

Литература

  • Подбельский В. В. Глава 10.2 Множественное наследование и виртуальные базовые классы // Язык Си++ / рец. Дадаев Ю. Г.. — 4. — М. : Финансы и статистика , 2003. — С. 336-359. — 560 с. — ISBN 5-279-02204-7 , УДК 004.438Си(075.8) ББК 32.973.26-018 1я173.
Источник —

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