Шаг 28.
Множественное наследование. Виртуальные базовые классы

    На этом шаге мы рассмотрим использование виртуальных базовых классов.

    Чтобы устранить дублирование объектов непрямого базового класса при множественном наследовании, этот базовый класс объявляют виртуальным. Для этого в списке базовых классов перед именем класса необходимо поместить ключевое слово virtual. Например, класс X будет виртуальным базовым классом при таком описании:

class X { ... f(); ... }; 
class Y: virtual public X { ... }; 
class Z: virtual public X { ... ); 
class D: public Y, public Z { ... );

    Теперь класс D будет включать только один экземпляр X, доступ х которому равноправно имеют классы Y и Z. Графически это очень наглядно:


Рис.1. Исключение дублирования базового класса

    Иллюстрацией сказанного может служить иерархия классов в следующей программе:

//OOР28_1.СРР - множественное наследование с виртуальным
//	базовым классом.
#include <iostream.h>
class base  //  Первичный (основной) базовый класс.
{
   int  jj;   
   char cc;  
   char w[10]; 
 public:
   base (int j = 0,   char c = '*')
      { jj = j; 
        cc = c;
      }
};

class dbase: public virtual base 
{ 
   double dd; 
 public:
   dbase(double d = 0.0) : base()
      { dd = d; }
};
class fbase:  public virtual base 
{ 
   float ff; 
 public:
   fbase (float f = 0.0):  base()
      { ff = f; }
};

class top:  public dbase,   public fbase 
{ 
   long tt; 
 public:
   top (long t = 0) : dbase(), fbase()
     { tt = t; }
};

void main ()
{ 
    cout <<"\nОсновной базовый класс: sizeof(base) = " << sizeof(base); 
    cout <<"\nНепосредственная база: sizeof(dbase) = " << sizeof(dbase); 
    cout <<"\nНепосредственная база: sizeof(fbase) = " << sizeof (fbase); 
    cout <<"\nПроизводный класс: sizeof(top) = " << sizeof(top); 
}
Текст этой программы можно взять здесь.

    Результаты выполнения программы:

   Основной базовый класс:   sizeof (base) = 13
   Непосредственная база:   sizeof(dbase) = 23
   Непосредственная база:   sizeof(fbase)	 = 19
   Производный класс:   sizeof (top)  =  33

    Основной базовый класс base в соответствии с размерами своих компонентов стандартных типов int и char [11] имеет размер 13 байт. Создаваемые на его основе классы dbase и fbase занимают соответственно 23 и 19 байт. (В dbase входит переменная типа double, занимающая 8 байт, наследуется базовый класс base, для которого требуется 13 байт, и 2 байта нужны для связи в иерархии виртуальных классов.) Производный класс top включает: один экземпляр базового класса base (13 байт); данные и связи класса dbase (10 байт); данные и связи класса fbase (6 байт); компонент long tt (4 байта).

    Если в той же программе убрать требование виртуальности (атрибут virtual) при наследовании base в классах dbase и fbase, то программа будет выглядеть так:

//OOР28_2.СРР - множественное наследование с невиртуальным
//	базовым классом.
#include <iostream.h>
class base  //  Первичный (основной) базовый класс.
{
   int  jj;   
   char cc;  
   char w[10]; 
 public:
   base (int j = 0,   char c = '*')
      { jj = j; 
        cc = c;
      }
};

class dbase: public base 
{ 
   double dd; 
 public:
   dbase(double d = 0.0) : base()
      { dd = d; }
};
class fbase:  public base 
{ 
   float ff; 
 public:
   fbase (float f = 0.0):  base()
      { ff = f; }
};

class top:  public dbase, public fbase 
{ 
   long tt; 
 public:
   top (long t = 0) : dbase(), fbase()
     { tt = t; }
};

void main ()
{ 
    cout <<"\nОсновной базовый класс: sizeof(base) = " << sizeof(base); 
    cout <<"\nНепосредственная база: sizeof(dbase) = " << sizeof(dbase); 
    cout <<"\nНепосредственная база: sizeof(fbase) = " << sizeof (fbase); 
    cout <<"\nПроизводный класс: sizeof(top) = " << sizeof(top); 
}
Текст этой программы можно взять здесь.

    А результаты будут такими:

   Основной базовый класс:   sizeof (base) = 13
   Непосредственная база:   sizeof(dbase) = 21
   Непосредственная база:   sizeof(fbase)	 = 17
   Производный класс:   sizeof (top)  =  42

    Обратите внимание, что размеры производных классов при отсутствии виртуальных базовых равны сумме длин их компонентов и длин унаследованных базовых классов. "Накладные расходы" памяти здесь отсутствуют.

    При множественном наследовании один и тот же базовый класс может быть включен в производный класс одновременно несколько раз, причем и как виртуальный, и как невиртуальный. Для иллюстрации этого положения изобразим направленный граф, а затем приведем структуру соответствующей ему иерархии классов:


Рис.2. Пример включения базового класса как виртуального и невиртуального

   

class X  {...};
class Y: virtual public X { ...};
class Z: virtual public X { ...};
class B: virtual public X { ...};
class C: virtual public X { ...};
class E: public X { ...};
class D: public X { ...};
class A: public D, public B, public Y, public Z, public C, public E {...};

    В данном примере объект класса A включает три экземпляра объектов класса X: один виртуальный, совместно используемый классами B, Y, C, Z, и два невиртуальных относящихся соответственно к классам D и Е. Таким образом, можно констатировать, что виртуальность класса в иерархии производных классов является не свойством класса как такового, а результатом особенностей процедуры наследования.

    Возможны и другие комбинации виртуальных и невиртуальных базовых классов. Например:

class ВВ  {   ...   };
class АА: virtual public ВВ { ... };
class CC: virtual public ВВ { ... };
class DD: public AA, public CC, virtual public BB { ... };

    Соответствующий НАГ имеет вид:


Рис.3. НАГ комбинации виртуальных и невиртуальных классов

    При использовании наследования и множественного наследования могут возникать неоднозначности при доступе к одноименным компонентам разных базовых классов. Простейший и самый надежный способ устранения неоднозначностей - использование квалифицированных имен компонентов. Как обычно, для квалификации имени компонента используется имя класса. Следующий пример иллюстрирует упомянутую неоднозначность и ее разрешение с помощью квалифицированных имен компонентов:

class X { public: int d; ... } ; 
class Y { public: int d; ... }; 
class Z: public X, public Y 
    { public:
           int d;
          .   .    .
          d = X::d + Y::d;
          .   .    .
    };

    На следующем шаге мы начнем знакомиться с виртуальными функциями.




Предыдущий шаг Содержание Следующий шаг