Когда виртуальные функции вызываются из производного класса, но не замещаются, то вызывается функция базового класса. Однако во многих случаях нет смыслового определения виртуальной функции в базовом классе. Например, в базовом классе Figure примера 33 для функции show_area() используется просто "заглушка" с сообщением. При создании библиотеки классов для виртуальных функций еще неизвестно, будет ли смысловое содержание в контексте базового класса. Существует два способа справиться с этой проблемой.
Первый — выдать предупреждающее сообщение. Но это полезно не во всех случаях. Например, может быть виртуальная функция, которая должна быть определена в производном классе, и иметь некоторое значение. В классе Figure площадь в общем случае не определена.
Другим решением этой проблемы в С++ являются чистые виртуальныефункции (pure) – это функции объявленные в базовом классе как виртуальные, но не имеющие описания. Производный класс должен определить свою собственную версию виртуальной функции, так как нельзя просто использовать версию базового класса. Иначе говоря, чистая виртуальнаяфункция – это пустое место, ожидающее своего заполнения производным классом. Объявляется чистая виртуальная функция, как и обычная виртуальная, но завершается =0.
Например, в классе:
class AbstractClass
{ public:
virtual void f1 (); // виртуальная функция
virtual void f2 () = 0; // чистая виртуальная функция
};
Компилятору не потребуется реализация функции f2, в отличие от остальных функций, поскольку она не содержит своего тела.
Если класс содержит хотябы одну чистую виртуальную функцию, он называется абстрактным классом (abstract class). Подобно любой абстрактной идее, абстрактный класс описывает нереализованные концепции.
|
Абстрактный класс имет одну важную черту – нельзя создать объектэтого класса. Например, нельзя объявить объект типа AbstractClass, иначе компилятор выдаст ошибку. Причина, по которой абстрактные классы не могут использоваться для объявления объекта та, что одна или более функций не имеют определения.
Это всего лишь схема, на основе которой можно создать производный класс. Он должен использоваться только как интерфейс и в качестве базового класса, от которого наследуются другие классы. Иначе говоря, абстрактный класс представляет собой интерфейс к множеству реализаций общего понятия. Конечно, другие функции-элементы класса AbstractClass могут вызывать чистую виртуальную функцию. Например, функция f1 может вызвать f2:
void AbstractClass:: f1()
{ f2 (); // вызвать чистую виртуальную функцию
...
}
Чтобы использовать абстрактный класс, следует вывести из него новый класс:
class Myclass: public AbstractClass
{ public:
virtual void f2 (); // бывшая чистая виртуальная функция
...
}
Производный класс Myclass наследует чистую виртуальную функцию, но объявляет ее без =0. В другом месте следует реализовать функцию-элемент f2:
void Myclass:: f2 ()
{... // операторы функции-элемента
}
Обращения к первоначальной чистой виртуальной функции-элементу, к примеру, в данном случае AbstractClass:: f1(), теперь преадресованы Myclass:: f2(). Чистые виртуальные функции подобны крючкам, на которые вы можете цеплять код производных классов. Теперь, когда все виртуальные функции-элементы реализованы, компилятор сможет воспринять объекты типа Myclass:
Myclass myobject;
|
Выполнение оператора myobject.f1 приведет к вызову наследованной функции f1, которая и вызовет f2 из Myclass.
Абстрактный класс не может быть использован как тип аргумента и функции show_ar ea(). В главной функции из объявления: Figure f, *p; необходимо уда лить объек т f и три строки операторо в с ним связанных, иначе компилятор укажет на ошибку создания объекта абстра ктного типа.
Результаты программы:
Треугольник с высотой 3 и основанием 4 имеет площадь = 6
Прямогольник со сторонами 5 и 6 имеет площадь = 30
Круг с радиусом 2 имеет площадь = 12.56
Производные классы с конструкторами и деструкторами
Каждый класс — базовый и производный может иметь конструктор и деструктонкция
virtual void draw()=0; // чистая виртуальная функция
} shape x; // ошибка: попытка создания объекта
// абстрактного класса
shape f(); // ошибка: абстрактный класс не может быть
// типом возврата
int q(shape s); // ошибка: абстрактный класс не может быть
// типом аргумента функции
shape* sp; // указатель на абстрактный класс допустим
shape& h(shape&); // ссылка на абстрактный класс в качестве типа
// возврата или аргумента функции допустима
Предположим, что В является производным классом от абстрактного базового класса А. Тогда для каждой чистой виртуальной функции fun() в А класс В должен обеспечивать определение fun, либо объявлять fun как чисто виртуальную функцию. Если этого не сделано, то компилятор сообщит об ошибке. Например, описание производного класса от Shape с замещением чисто виртуальных функций:
class Circle: public Shape // производный класс
{ int radius;
public:
virtual void rotate(int) { } // действия отсутствуют
|
void draw (); // замещение Shape:: draw
};
Circle c;
Абстрактные классы часто не предназначены для создания дальнейших производных классов. Порождение классов чаще всего используется для реализации. Однако из абстрактного класса можно сконструировать новый интерфейс, создав производный от него расширенный абстрактный класс. Тогда новый абстрактный класс должен в свою очередь быть реализован путем создания производного неабстрактного класса.
Чисто виртуальная функция, которая не определена в производном классе, остается чисто виртуальной, поэтому такой производный класс, как и базовый, является абстрактным. Это позволяет строить реализацию поэтапно:
class Poligon: public Shape // новый абстрактный класс
{ public:
void rotate(int); // замещение Shape:: rotate
// draw не замещен
};
Poligon p; // ошибка: объявление объекта абстрактного класса
class Newpoligon: public Poligon // производный класс
{ public:
void rotate(int); // замещение Shape:: rotate
void draw (); // замещение Shape::draw
};
Newpoligon np; // правильно
Пример 35.
Рассмотрим модифицированную версию программы примера 33. В классе Figure функцию show_area() определим как чистую виртуальную:
class Figure // базовый класс
{ protected: // защищенные элементы, доступные в производных классах
double x, y;
public:
void set_dim (double i, doublej=0) // 2-й параметр по умолчанию
{ x = i, y = j; } // задание измерений фигур
virtual void show_area() = 0; // чистая виртуальная функция
};
В производных классах остаются переопределенные функции show_area(). В главной функции из объявления: Figure f, *p; необходимо удалить объект f и три строки операторов с ним связанных, иначе компилятор укажет на ошибку создания объекта абстрактного типа.
Результаты программы:
Треугольник с высотой 3 и основанием 4 имеет площадь = 6
Прямогольник со сторонами 5 и 6 имеет площадь = 30
Круг с радиусом 2 имеет площадь = 12.56