В C++ иерархии классов могут вызывать проблему ромба (diamond problem), когда от одного базового класса наследуются два потомка, а затем создаётся ещё один класс, наследующийся от этих двух. В таком случае объект класса-наследника будет содержать две независимые копии базового класса. Для решения этой проблемы в C++ и реализовано виртуальное наследование.
Если использовать обычное множественное наследование:
class A { public: int x; }; class B : public A {}; class C : public A {}; class D : public B, public C {};
Объект D будет содержать 2 копии A: одну через B, другую — через C. Это вызывает неоднозначности доступа к A::x и неоправданный расход памяти.
Виртуальное наследование устраняет дублирование базового класса. Один экземпляр базового класса A будет использоваться всеми потомками:
class A { public: int x; }; class B : public virtual A {}; class C : public virtual A {}; class D : public B, public C {};
Теперь D содержит только одну копию A, а неоднозначность с доступом к A::x устраняется.
Ключевые особенности:
Почему нельзя решать проблему ромба с помощью явного указания пути через scope resolution?
Scope resolution решает только неоднозначность доступа к членам базового класса, но не убирает лишние копии самого базового класса. Проблема двойной инициализации и двойного хранения данных остаётся.
В каком порядке вызываются конструкторы при виртуальном наследовании?
Конструкторы виртуальных базовых классов вызываются в первую очередь и только один раз, причём непосредственно конструктором самого последнего в иерархии наследования класса.
Пример:
class A { public: A() { std::cout << "A "; } }; class B : public virtual A { public: B() { std::cout << "B "; } }; class C : public virtual A { public: C() { std::cout << "C "; } }; class D : public B, public C { public: D() { std::cout << "D "; } }; D d; // Вывод: A B C D
Обязательно ли объявлять виртуальное наследование и при объявлении, и при определении класса?
Да, виртуальное наследование должно быть указано везде, где идёт наследование от соответствующего базового класса. Ошибка компиляции иначе неизбежна, а множественное наследование станет обычным.
Сложная иерархия, в которой не применено виртуальное наследование, из-за чего в объекте оказывается две копии настроек:
Плюсы:
Минусы:
Использовано виртуальное наследование с согласованным конструктором самого верхнего потомка:
Плюсы:
Минусы: