4.6. Виртуальные классы

Базовый класс называется виртуальным, если его поля не дублируются при неоднократном наследовании. Виртуальный базовый класс объявляется при наследовании при определении производного класса следующим образом:

сlass имя_производного_класса:

virtual public имя_виртуального_базового_класса

{

тело производного класса;

}

Пример. На рис. 4.3 приведена иерархия производных класса четырёхугольника. Для того чтобы поля четырехугольника не наследовались более одного раза,  объявим его как виртуальный базовый класс. Ромб будем задавать с помощью центра, длин диагоналей и угла поворота вокруг первой диагонали.

Ниже для иллюстрации понятия виртуального класса приведём текст программы, в которой определена иерархия классов, отраженная на рис. 4.3. Класс квадрата дважды наследует координаты четырёх углов.

#include <graphics.h>

#include <math.h>

#include <conio.h>

class four                  // четырехугольник

{

protected:

float x[4], y[4];          // координаты вершин

public:

four(){}

four(float *ix, float *iy) // конструктор

{

       int i;

       for(i=0; i<4; i++)

       {

             x[i] = ix[i]; y[i] = iy[i];

       }

}

~four() {delete x; delete y;} //деструктор

void show();                    //вывод четырёхугольника на экран

};

class rect: public virtual four               // прямоугольник

{

protected:

int xleft, xright, ytop, ybottom;

public:

rect(int x1, int y1, int x2, int y2):

xleft(x1), ytop(y1), xright(x2), ybottom(y2) // конструктор

{

       x[0] = x1; y[0] = y1;

              x[1] = x1; y[1] = y2;

       x[2] = x2; y[2] = y2;

       x[3] = x2; y[3] = y1;

}

};

//класс ромба

class romb: public virtual four

{

protected:

float xc, yc, alpha, a, b;

public:

romb(float x1, float y, float ugol, float d1, float d2)

{

       xc = x1; yc = y; alpha = ugol; a = d1; b = d2;

       x[0] = xc + (a/2)*cos(alpha);

       x[0] = yc + (a/2)*sin(alpha);

       x[1] = xc — (b/2)*sin(alpha);

       x[1] = yc + (b/2)*cos(alpha);

       x[2] = xc — (a/2)*cos(alpha);

       x[2] = yc — (a/2)*sin(alpha);

       x[3] = xc + (b/2)*sin(alpha);

       x[3] = yc — (b/2)*cos(alpha);

}

};

// стороны квадрата параллельны осям координат

class square : public rect, public romb

{

int xcenter, ycenter; // центр квадрата

int size;             // сторона квадрата

public:

square(int x0, int y0, int s): xcenter(x0+s/2), ycenter(y0+s/2),

size(s), rect(x0 — s/2, y0 — s/2, x0 + s/2, y0 + s/2),

romb(x0, y0, 3.14159/4, s, s) {show();}

};

void four :: show()     // вывод четырехугольника

{

int i;

moveto (x[3], y[3]);

for(i=0; i<4; i++)

lineto(x[i], y[i]);

};

void main()

{

int gd = DETECT, gm;

initgraph (&gd, &gm,"c:\prog\bc31\bgi");

square q(320, 240, 100);.

getch();    //ожидание нажатия любой клавиши

}

В результате работы программы на экран будет выведен квадрат, сторона которого равна 100, а центр квадрата совпадает с центром экрана.

Конструкторы и деструкторы виртуальных классов. Включение полей виртуального базового класса в производный класс осуществляется один раз, а их инициализация будет происходить в таком его производном классе, который не является его непосредственным наследником.

Во время инициализации объекта некоторого класса А конструкторы классов, наследованных виртуально, активизируются перед конструкторами всех остальных базовых классов этого класса. Происходит это следующим образом:

· если в списке инициализации конструктора класса А используется инициализатор базового класса, наследованного виртуально, то активизируется конструктор этого базового класса;

в противном случае конструктор виртуального базового класса инициализируется без параметров.

·

Деструкторы активизируются в обратном порядке.

Пример. Рассмотрим программу, работающую с иерархией классов, приведенных на рис. 4.4. Эта программа иллюстрирует порядок вызова конструкторов виртуальных классов.

Ниже приведён текст программы:

#include <stdio.h> //стандартная библиотека ввода-вывода

//классы реализованы посредством конструкторов и информационных полей

class V

{

public:

int a,b,c;

V(): c(3){};

V(int p): a(p){};

};

class A: virtual public V

{

public:

A():V(3) {a=1;}

};

class B: virtual public V

{

public:

B() {b=2;}

};

class C: public A,B

{

public:

//функция вывода

void out(){printf("a=%d b=%d c=%dn",a,b,c);}

};

int main()

{

C ob1;

ob1.out();

return 0;

}

При создании объекта ob1 класса С конструктор класса С вызовет конструктор V(), который установит С=3, затем конструкторы А() и В() объектов А и В, не имеющие параметров. Затем конструктор класса А вызовет конструктор виртуального базового класса V(3), который установит а = 3, но в теле конструктора класса А будет произведено присваивание а = 1, в результате чего а станет равен 1. Затем будет вызван конструктор класса В, который установит b = 2.

Результаты работы программы

a=1  b=2  c=3

Пример. Рассмотрим иерархию классов, приведённую на рис. 4.5.

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

Ниже приведён текст программы.

#include <stdio.h>       //стандартная библиотека ввода-вывода

class V1 //первый класс

{

friend class D;   //дружественный класс

friend class B;   //дружественный класс

int fix1;

public:

V1(int val): fix1(val){};    //конструктор

V1(): fix1(10){};

};

class V2 //второй класс

{

friend class D;   //дружественный класс

int fix2;

public:

V2(): fix2(20){}; //конструктор

V2(int p): fix2(p){};

};

//схема наследования

class B: virtual public V1, virtual public V2 {};

class C: virtual public V1, virtual public V2 {};

class D: public B,C {

public:

D(): V1(30){};

D(int p): V2(p){};

//функция вывода

  void out(){printf("fix1=%d fix2=%d n",fix1,fix2);}

};

int main()

{

D ob1; D ob2(100);

ob1.out();

ob2.out();

return 0;

}

При создании объекта ob1 будут вызваны конструкторы V1(30) и V2(), которые установят fix1 = 30 и fix2 = 20. При создании объекта ob2 будет вызван конструктор V1() без параметра, а затем – конструктор V2(100). Они установят fix1 = 10 и       fix2 = 100.

Результаты работы программы

fix1=30  fix2=20

fix1=10  fix2=100

Пример. Рассмотрим иерархию классов, приведённую на рис. 4.6. Сначала вызываются конструкторы V1,V2,V3, а затем – B,C,D.

Ниже приведён текст программы, иллюстрирующей вышеприведённую иерархию.

#include <stdio.h> //стандартная библиотека ввода-вывода

//построения иерархии

class V1 //класс V1

{

friend class D;   //дружественный класс

int fix1;

public:

V1(int val): fix1(val){};

V1(): fix1(10){};

};

class V2 //класс V2

{

friend class D;   //дружественный класс

int fix2;

public:

V2(): fix2(20){};

};

class V3 //класс V3

{

int fix3;

friend D;

public:

V3(): fix3(40){};

V3(int p): fix3(p){};

};

//схема наследования

class A: virtual public V1 {};

class B: virtual public V1 {};

class C: virtual public V2, virtual public V3 {};

class D: public B,C {

public:

D(): V1(30){};

D(int p): V3(p){};

//функция вывода

void out(){printf("fix1=%d fix2=%d fix3=%dn",fix1,fix2,fix3);}

};

int main()

{

D ob1; D ob2(100);

ob1.out();

ob2.out();

return 0;

}

Результаты работы программы

fix1=30  fix2=20  fix3=40

fix1=10  fix2=20  fix3=100