Виртуальные базовые классы
Поскольку класс может несколько раз выступать как косвенный базовый класс к производному классу, в C++ имеется способ оптимизировать функционирование таких базовых классов. Виртуальные базовые классы позволяют экономить пространство и исключать неоднозначности в иерархиях классов, в которых используется множественное наследование.
Каждый невиртуальный объект содержит копию элементов данных, определенных в базовом классе. Такой повтор данных приводит к ненужному увеличению их объема. Кроме того, при каждой попытке обращения к элементам базового класса приходится указывать, какая именно их копия требуется.
Если базовый класс определен как виртуальный базовый класс, то он может несколько раз выступать как косвенный базовый класс без дублирования элементов данных. Единственная копия его элементов данных совместно используется всеми базовыми классами, которые используют его как виртуальный базовый класс.
Когда создается объявление виртуального базового класса, в базовых списках производных классов указывается ключевое слово virtual.
Рассмотрим иерархию классов, представленную на следующем рисунке, на котором показана имитация графа Lunch-Line.
Смоделированный граф Lunch-Line
Как видно на рисунке, класс Queue является базовым для двух других классов: CashierQueue и LunchQueue. Однако когда эти два класса объединяются и образуют класс LunchCashierQueue, возникает следующая проблема: новый класс содержит два подчиненных объекта типа Queue — один из CashierQueue, а другой из LunchQueue. На следующем рисунке показана концептуальная структура памяти (фактическая структура памяти может быть оптимизирована).
Смоделированный объект Lunch-Line
Обратите внимание, что в объекте LunchCashierQueue имеется два подчиненных объекта Queue. В следующем коде содержится объявление Queue как виртуального базового класса:
// deriv_VirtualBaseClasses.cpp
// compile with: /LD
class Queue {};
class CashierQueue : virtual public Queue {};
class LunchQueue : virtual public Queue {};
class LunchCashierQueue : public LunchQueue, public CashierQueue {};
Благодаря ключевому слову virtual будет включена только одна копия подчиненного объекта Queue (см. следующий рисунок).
Имитация объекта Lunch-Line с виртуальными базовыми классами
Класс может иметь как виртуальный, так и невиртуальный компонент заданного типа. Это происходит при условиях, которые иллюстрирует следующий рисунок.
Виртуальные и невиртуальные компоненты одного класса
На этом рисунке показано, что классы CashierQueue и LunchQueue используют Queue как виртуальный базовый класс. Однако TakeoutQueue определяет Queue в качестве базового класса, а не виртуального базового класса. Поэтому в LunchTakeoutCashierQueue имеется два подчиненных объекта типа Queue: один из пути наследования, включающего LunchCashierQueue, а второй из пути, включающего TakeoutQueue. Это показано на следующем рисунке.
Структура объектов с наследованием от виртуальных и невиртуальных базовых классов
Примечание
Наследование от виртуальных базовых классов позволяет существенно сократить объем данных по сравнению с наследованием от невиртуальных классов.Однако оно может породить дополнительные затраты на обработку.
Если производный класс переопределяет виртуальную функцию, которую он наследует от виртуального базового класса, и если конструктор или деструктор производного базового класса вызывает эту функцию при помощи указателя на виртуальный базовый класс, то компилятор может вставить дополнительные скрытые поля vtordisp в классы с виртуальными базовыми классами. Параметр компилятора /vd0 отключает добавление скрытого члена смещения конструктора или деструктора vtordisp. Параметр компилятора /vd1 (по умолчанию) разрешает добавлять их по мере необходимости. Добавление полей vtordisps следует отключать только в том случае, если точно известно, что все конструкторы и деструкторы классов вызывают виртуальные функции виртуально.
Параметр компилятора /vd действует на весь модуль компиляции. Используйте директиву #pragma vtordisp, чтобы отключить, а затем повторно включить добавление полей vtordisp для каждого класса по отдельности:
#pragma vtordisp( off )
class GetReal : virtual public { ... };
#pragma vtordisp( on )