yandex rtb 1
ГоловнаЗворотній зв'язок
yande share

Объектно-ориентированное программирование

Лекция 9.

Виртуальные функции. Абстрактные классы.

 

Как было продемонстрировано ранее, перегруженная функция-член вызывается с учетом соответствия типов. Компилятор связывает вызов функции с тем ее вариантом, который соответствует классу, указанному при объявлении указателя, а не тому, на объект которого направлен указатель. В языке С++ имеется возможность динамически (в процессе выполнения) выбирать перегруженную функцию-член среди функций базового и производного классов. Рассмотрим несколько измененный вариант программы из предыдущей лекции.

 

#include <iostream.h>

class rectangle {

protected:

   float x,y,height,width;

public:

   rectangle(float xbase, float ybase, float h, float w):

     x(xbase), y(ybase), height(h), width(w) { }

     float area() { return height*width; }

   virtual void printarea() {

               cout<<"rectangle area="<<area()<<endl;     

   }

};

class colorsquare : public rectangle {

private:

   int color;

public:

   colorsquare(float xbase, float ybase, float h, int c):

     rectangle(xbase,ybase,h,h), color(c) { }

     int getcolor() {return color;}

     void printarea() {

                 cout<<"square area="<<rectangle::area()<<endl;

     }

};

void main() {

   rectangle a(0,0,10,20),*refa;

   refa=&a;

   refa->printarea();

   colorsquare b(0,0,10,0xffffffff), *refb;

   refb=&b;

   refb->printarea();

   refa=refb;

   refa->printarea();

}

 

Единственное отличие – в объявлении функции printarea базового класса. Ключевое слово virtual служит спецификатором функции и как раз предоставляет механизм динамического выбора перегруженных функций. Результат работы данной программы следующий:

 

rectangle area=200

square area=100

square area=100

 

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

Виртуальность является частью полиморфизма. Механизм виртуальных функций основан на позднем (динамическом) связывании (раннее – связывание на этапе компиляции). Если в некотором классе имеется хотя бы одна виртуальная функция, то все объекты этого класса содержат указатель на связанную с их классом виртуальную таблицу указателей функций этого класса. Доступ к виртуальной функции осуществляется посредством косвенной адресации – через этот указатель и соответствующую таблицу. Тем самым использование виртуальных функций снижает быстродействие и увеличивает размер объектов класса.

При отсутствии функции-члена производного типа по умолчанию используется виртуальная функция базового класса. В нашем примере при отсутствии определения функции printarea в классе colorsquare всегда будет вызываться эта же функция базового класса.

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

Деструкторы могут быть виртуальными. Если класс имеет хотя бы одну виртуальную функцию, рекомендуется деструкторы также объявлять виртуальными.

Дополним базовый класс rectangle деструктором:

 

   virtual ~rectangle() {cout<<"in rectangle\n";}

 

Также добавим деструктор в производный класс (виртуальность в нем объявлять уже нет необходимости):

 

     ~colorsquare() {cout<<"in colorsquare\n";}

 

Определим главную функцию таким образом, чтобы продемонстрировать динамическое создание и уничтожение объектов с помощью операторов new и delete:

 

void main() {

   rectangle *refa;

   refa=new rectangle(0,0,10,20);

   colorsquare *refb;

   refb=new colorsquare(0,0,10,0xffffffff);

   delete refa;

   refa=refb;

   delete refa;

}

 

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

 

in rectangle

in colorsquare

in rectangle

 

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

 

in rectangle

in rectangle

 

Здесь при уничтожении объекта производного класса вызывается деструктор только базового класса.

 

Иерархия типов обычно имеет корневой класс, содержащий некоторое число виртуальных функций. При этом они в большинстве случаев являются фиктивными функциями. Они имеют пустое тело в корневом классе, но в производных классах этим функциям придают смысл (в терминологии ООП это называется отложенным методом). Для таких функций в С++ введено понятие «чисто виртуальные функции» – это виртуальные функции, тело которых не определено. Объявляются они следующим образом:

 

virtual прототип_функции=0;

 

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

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

 

class shape {

protected:

   float dim1,dim2;

public:

   virtual float area()=0;

   shape(float d1,float d2) : dim1(d1), dim2(d2) { }

};

class rectangle : public shape{

protected:

   float x,y;

public:

   rectangle(float xbase, float ybase, float h, float w):

     shape(w,h), x(xbase), y(ybase) { }

     float area() { return dim1*dim2; }

   virtual void printarea() {

               cout<<"rectangle area="<<area()<<endl;     

   }

   virtual ~rectangle() {cout<<"in rectangle\n";}

};

 

Мы использовали предыдущую программу со следующими изменениями:

-    добавили абстрактный класс shape, имеющий виртуальную функцию area и конструктор

-    изменили класс rectangle, сделав его производным от shape. При этом изменился конструктор класса и по-новому определена функция area.

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

 

 

 

11