Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Курс лекцій.doc
Скачиваний:
15
Добавлен:
03.11.2018
Размер:
1.12 Mб
Скачать

8.3 Віртуальні деструктори.

Деструктори, на вiдмiну вiд конструкторiв, можуть бути вiртуаль-ними. Синтаксично вони задаються як і звичайні віртуальні функціх за допомогою ключового слова virtual. Вiртуальнi деструктори використовуються у випадку, коли в деякому класi необхiдно знищити об'єкти похiдного класу, на якi посилаються вказiвники на базовий клас.

Розглянемо приклад:

class TBase{

private:

char*sp1;

public:

TBase(const char*s)

{sp1=strdup(s);}

virtual~TBase( )

{delete sp1;} };

class TDerived::public TBase{

private:

char*sp2;

public:

TDerived(const char*s1,const char*s2)/:TBase(s1)

{sp2=strdup(s2);}

virtual~TDerived( )

{delete sp2;}};

TBase*pbase;

pbase=new TDerived("string1","string2");

delete pbase; // s1-знищився,s2-залишився.

Якщо в даному прикладi деструктори не були б вiртуальними, то при виходi з областi видимостi об'кта класу Tderived, на який вказує вказiвник pbase, викличеться лище деструктор класу Tbase, тобто копiя рядка string2 залишиться в пам'ятi. Якщо ж деструктори оголошенi як вiртуальнi, то в данiй ситуацiї буде викликаний спочатку деструктор похiдного класу, а потiм i деструктор базового.

8.4 Посилання як засіб для реалізації поліморфізму

Для реалiзацiї полiморфiзму в С++, крiм вказiвників можна використовувати і посилання. Розглянемо наступний приклад:

#include "shape.h"

main

{circle c1;

Line l1,l2;

shape & pic0=c1

shape & pic1=l1;

shape & pic2=l2;

pic0.Draw ( );

pic1.Draw ( );

pic2.Draw ( );}

В цьому прикладі кожен виклик функції Draw() призводить до малювання відповідної фігури.

При передачi параметрiв вiртуальним функцiям може виникнути помилка. Виникає питання: коли здійснюється контроль помилок при передачі параметрів? Адже конкретна реалізація віртуальної функції зв’язується з об’єктом на етапі виконання програми. Що стосується С++, то контроль помилок при передачi параметрiв вiртуальним функцiям здiйснюється на етапi компiляцiї , а не виконання програми, тому що сигнатури вiртуальних функцiй повиннi жорстко спiвпадати.

8.5 Технічна реалізація механізму віртуальних функцій.

Екземпляр класу в С++ являє собою неперевну область в пам'ятi. Вказiвник на такий об'єкт мiстить початкову адресу цiєї областi. Коли викликається функцiя-член(об'єкту посилається повiдомлення), то цей виклик компiлюється у звичайний виклик функцiї з додатковим аргументом, який містить вказiвник на цей об'єкт. Тобто якщо в нас є такий виклик функції:

classname*object;

object->message(15);

то компiлятором він перетворюється в такий:

classname_mesage(object ,15);

Механiзм вiртуальних функцiй в С++ забезпечується за допомогою так званих таблиць віртуальних функцій(ТВФ).

1. ТВФ будується компiлятором автоматично для кожного класу, який має вiртуальнi функцiї i мiстить адреси вiртуальних функцiй, доступних в цьому класi;

2. Кожний екземпляр класу мiстить скритий вказiвник на його ТВФ;

3. Компiлятор автоматично вставляє в початок конструктора класу

фрагмент коду, який iнiцiалiзує вказiвник на ТВФ кожного класу;

4. Для будь-якої iєрархiї класiв адреса деякої вiртуальної функцiї має одне i теж змiщення в ТВФ кожного класу;

5. Пiд час виклику вiртуальних функцiй код, згенерований компiлятором, перш за все знаходить вказiвник вiртуальної таблицi, потiм вiдбувається звертання до ТВФ та знаходиться адреса вiртуальної функцiї i лише пiсля цього вiдбувається безпосереднiй виклик функцiї.

Приклад:

class Parent

{int value;

public:

virtual int method1(float r);

virtual void method2(void);

virtual float method3(char*s);

};

class child1:public Parent

{

public:

virtual void method2(void);

}

class child2:public child1

{

public:

virtual float method3(char*s);

}

Стосовно цього прикладу ТВФ можна уявити так:

//розглянемо клас Parent:

virtual_table1->

parent::method1

parent::method2

parent::method3

//для Child1:

virtual_table2->

parent::method1

child1::method2

parent::method3

//Беремо до уваги однаковість зміщення.

//для Child2:

virtual_table3=>

parent::method1

child1::method2

child2::method3

Тоді виклик виду:

child2 *c;

c->method3(string");

компiлятор перетворює в наступний:

(*(c=>virtual_table3[2]))(c,"string");