вернуться в оглавление предыдущая глава предыдущий параграф следующий параграф следующая глава


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

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

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

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

virtual void print() const;
//////////////////////////////////////////////
void main ()
{
   // виртуальные функции важны, когда создаются указатели на класс 
	  Detail *p=new Lens; 
}
//////////////////////////////////////////////

Реализация функции в базовом классе не изменится, а классе-наследнике и реализация и описание функции останется прежним.

Использование виртуальных функций важно, когда создается экземпляр указателя на класс-наследник, если при этом экземпляр описан как базовый класс, но создается как наследник.

Detail *p=new Lens; 

При этом:

  • Если должны вызываться "родные" функции-члены классов, то используется обычная перегрузка
    l.print(); // Detail::print 
  • Если функция-член базового класса должна подменяться функцией-членом класса-наследника, то при ее объявлении используется модификатор virtual
    l.print(); // Lens::print 
    l.print(); // Lens::print
  • Если должны вызываться последовательно обе функции, то это необходимо сделать принудительно вызвав функцию-член базового класса
    Detail::print();

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

5.2.2. Абстрактные классы

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

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

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

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

Рассмотрим как будет выглядеть описание абстрактного базового класса Деталь из примера 5.1. Реализация базоваго класса, и описание и реализация классов-наследников не изменятся.

/////////////////////////////////////////////////////////////////////////////
// класс Деталь - базовый класс для всех оптических деталей
class Detail
{
protected:
    // координата детали по оси z
    double m_z; 
    // диаметр детали
    double m_D;

public:
    // конструкторы и деструктор
    Detail();
    Detail(double z, double D);
    virtual ~Detail();

    // установить диаметр детали
    void Set_D(double D); 
    // получить диаметр детали
    double Get_D() const; 
    // установить координату по оси z
    void Set_z(double z); 
    // получить координату по оси z
    double Get_z() const; 

    // печать параметров детали
    virtual void print() const;	
    // вычисление хода луча через деталь 
    // должно быть реализовано во всех классах-наследниках
    virtual void RayTrace() = 0;
}; 
/////////////////////////////////////////////////////////////////////////////

Тестирующая функция для абстрактного класса:

///////////////////////////////////////////////////////////////////////
void main()
{
	// массив указателей на детали 
	vector<Detail*> details(2);
	// первый элемент - линза
	details[0] = new Lens;
	// второй элемент - зеркало
	details[1] = new Mirror;
	
    // печать всех деталей
	cout<<"-------------------------"<<endl;
	for(size_t i=0; i<details.size(); ++i)
	{
		details[i]->print();
	}
	
    // расчет хода луча через все детали
	cout<<"-------------------------"<<endl;
	for(size_t i=0; i<details.size(); ++i)
	{
		details[i]->RayTrace();
	}

    // освобождение памяти для всех деталей (вызов деструкторов)
	cout<<"-------------------------"<<endl;
	for(size_t i=0; i<details.size(); ++i)
	{
		delete details[i];
	}
}
/////////////////////////////////////////////////////////////////////////////