Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учебное пособие 3000101.doc
Скачиваний:
8
Добавлен:
30.04.2022
Размер:
370.18 Кб
Скачать

10.9. Виртуальные базовые классы

Для задания виртуального базового класса используется ключевое слово virtual перед его именем:

class A : virtual public B { . . . };

class C : protected virtual A { . . . };

Объект производного класса будет содержать вместо данных от базового виртуального класса указатель на базовую часть.

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

Правила доступа к членам виртуальных классов те же, что и для невиртуальных классов.

10.10. Виртуальные функции

Для обычных членов-функций класса на этапе компиляции или компоновки происходит связывание вызова функции с ее определением. Виртуальная функция определяется в базовом классе, а в любом производном может переопределиться. Виртуальная функция вызывается только через указатель или ссылку на базовый класс. Какая именно функция будет вызвана, определяется на этапе выполнения программы и зависит от класса объекта, для которого она вызвана. Такой механизм называется динамическим связыванием или разрешением типов во время выполнения.

Чтобы создать виртуальную функцию, надо: поместить эту функцию в базовый класс; сделать зависящим от потомка способ реализации функции.

Разберемся с причинами, ведущими к необходимости создания виртуальных функций. Пусть имеется иерархия классов /4/: Device – предок классов Display, Keyboard, Mouse; класс Display – предок классовTDisplay, Gdisplay, класс Mouse – предок классов Gmouse, Tmouse.

Class Device {

protected :

int type;

int exist;

int state;

public :

Device (void);

Device (void);

int IsType(void); // тип устройства

int IsExist(void); // существование

char *IsName(void); // имя

void Init(void); }; // инициализация

// Каждый потомок имеет свои функции IsType, Init.

// Требуется создать класс, описывающий список устройств:

class DeviceList {

private:

static DeviceList *head; // указатель на начало списка

Device *object; // указатель на объект устройств

int objectType; // тип устройств

DeviceList *next; // указатель на следующий объект

public:

DeviceList ( Device* , int ); . . .

DeviceList *IsHead (void); // указатель на начало списка

DeviceList *IsNext(void); // указатель на следующий элемент

int IsType(void); // тип объекта (устройства)

Device *IsObject(void); //указатель на текущее устройство

DeviceList add( Device, int, DeviceList ); };

// Для статической переменной head определяется начальное значение

DeviceList *DeviceList::head = NULL;

DeviceList::DeviceList( Device *dp, int dt ) {

// dp – указатель на устройство, dt – само устройство

object = dp; // объект – это указатель текущее устройство

object Type = dt; // текущий тип устройства

next = NULL; // следующего устройства пока нет

if(head==Null) // если устройство – первое в списке,

head=this ; }// head – указатель на это устройство

// Добавление очередного устройства

DeviceList *DeviceList::add( Device *dp, int dp, DeviceList *p ) {

// dp- указатель на текущее устройство; dt – само устройство;

// p- указатель на текущий элемент в списке

if ( p == NULL ) // элементов в списке нет

p = new Device( dp, dt );

else if ( p->object == NULL )

{ p->object=dp; p ->objectType=dt;}

else p->next=add(dp, dt, p->next ); return p; }

// Создание списка объектов можно записать так:

enum{ IsTDisplay, IsGDisplay, IsKeyboard, IsTMouse, IsGMouse };

Tdisplay tdis;

Gdisplay gdis;

Keyboard kb;

Tmouse tm;

Gmouse gm;

DeviceList *l = new DeviceList( &tdis, IsTDisplay );

l->add(&kb,IsKeyboard,l->IsHead());

l->add(&tm,IsTMouse,l->IsHead()); . . .

// Внешняя функция, инициализирующая список объектов:

void InitDevice( DeviceList * lp){ // lp – указатель на список устройств

for (DeviceList *p = lp; p; p ->IsNext( ) )

{Device *po=p->IsObject( );

if ( po != NULL )

switch (p ->IsType( ) ) {

case IsTDisplay:

((Tdisplay *)po)->Init();

break;

case IsKeyboard:

((Keyboard *)po) ->Init( ) ; . . . } } }

Для инициализации всех объектов списка используется оператор switch. В классе DeviceList введена переменная int ObjectType, показывающая тип устройства. Для пользования программой надо точно знать иерархию потомков. Любое изменение иерархии вызовет изменения в программе. Облегчить жизнь программиста позволяют виртуальные функции.

В базовом классе Device функция Init описывается как виртуальная:

Class Device {

. . .

public :

. . .

virtual void init( void ); . . . };

В каждом потомке класса Device функция Init специфична в соответствиями с конкретным устройством. Но т. к. функция Init описана как virtual, то вызов p->Init() в программе определяется на этапе выполнения, когда p определяет конкретный объект. В зависимости от того, какой объект определяет p, будет вызвана соответствующая функция инициализации. Поэтому внешняя функция InitDevice значительно упростится:

void InitDevice(DeviceList *lp) {

for( DeviceList+p=lp ;p ; p->IsNext())

{Device *po=p->IsObject( );

if( po != NULL ) po->Init( ); }}

В базовом классе обычно виртуальная функция никак не определяется. Чтобы подчеркнуть это, используется запись:

virtual int f( void ) = 0;

virtual void init( void ) = 0;

Такие функции называются чистыми виртуальными функциями.

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

Если в предке функция объявлена как чистая виртуальная, то в потомке функция должна быть определена, либо опять объявлена чистой виртуальной. Если в базовом классе есть определение виртуальной функции, то в потомке она может наследоваться, либо переопределяться. Если при переопределении в потомке у функции изменен тип возврата, тип или количество параметров, то такая функция не будет виртуальной.

Class Device {

. . .

public:

virtual void Init( void )=0; . . .};

class Display : public Device {

friend class TextMouse ; . . .};

class Mouse: public Device{

. . .

public:

virtual void Init( void )=0; . . .};

class TextMouse : public Mouse {

public :

virtual void Init( void ); };

void TextMouse::Init( void ) { . . . }

В Device объявлена чистая виртуальная функция Init. В Mouse Init также чистая виртуальная функция. Mouse и Device – абстрактные классы. В классе TextMouse задано определение виртуальной функции Init, причем здесь virtual можно и не писать.

Вызывается виртуальная функция через ссылку или указатель на базовый класс, в котором она впервые объявлена. Виртуальные функции подчиняются тем же правилам видимости, что и обычные функции.

Пусть определена иерархия классов BAC, и в классе В функция f – чистая виртуальная функция. Допустим, класс А должен использоваться для создания объектов и в нем необходимо задать чистую виртуальную функцию f. Тогда в классе A надо определить пустую функцию f:

class A : public b {

public:

void f( void ) { } ; // пустая функция

} ;

Вызывается виртуальная функция через ссылку или указатель на базовый класс, в котором она впервые объявлена.

Виртуальные функции подчиняются тем же правилам видимости, что и обычные функции.