И.А. Волкова, А.В. Иванов, Л.Е. Карпов - Основы объектно-ориентированного программирования. Язык программирования С++ (ЧБ) (1114895), страница 13
Текст из файла (страница 13)
При этом основные алгоритмы будут реализованы в базовом классе. В классе-наследнике эти алгоритмыиспользуются путем вызова соответствующих методов, реализованных в базовомклассе, с передачей им необходимых параметров, соответствующих даннымконкретного вида.Приведем представление класса для хранения данных типа символьныхстрок, а также реализацию функций comp() для сравнения таких данных иout() для вывода данных конкретного типа, хранящихся в вершине дерева:class s_tree: private gen_tree {public:s_tree () {}void insert (char * d) {gen_tree::insert (d);}char * find (char * d) const{return (static_cast<char *>(gen_tree::find (d)));}void print() const {gen_tree::print ();}};int comp (void * i, void * j){return (strcmp (static_cast<char*> (i),static_cast<char*> (j)));}void out (bnode * r) {cout << static_cast<char*> (r -> data) << " : ";cout << r -> count << "";}70Примечание. strcmp() – стандартная процедура сравнения символьных данных,содержащаяся в библиотечном файле string.h.При так описанной реализации методов insert() и print() в базовомклассе данные из дерева выводятся в отсортированном виде.
Так, при следующемнаборе операций ввода в дерево:s_tree s1;s1.insert("4");s1.insert("2");s1.insert("1");s1.insert("3");s1.insert("6");s1.insert("5");s1.insert("7");s1.print();В выходной поток будет выдана следующая строка:1 : 12 : 13 : 14 : 15 : 16 : 17 : 1Если реализация методов print() будет следующей:int lev;void gen_tree::print0 () const {lev=0;print0 (root);}void gen_tree::print (bnode* r) const {int pp;if (r != 0){out (r);lev ++;print0 (r -> right);cout << '\n';for (pp = 0; pp < lev; pp ++)cout << "";print0 (r -> left);lev --;};}То дерево будет выведено в виде иерархической структуры, повернутой на90 :o4 : 16 : 12 : 17531::::11117111.Динамический полиморфизм, механизм виртуальных функцийВ C++ введено понятие виртуальных функций (методов). Механизм виртуальных методов заключается в том, что, результат вызова виртуального методас использованием указателя или ссылки зависит не от того, на основе какого типасоздан указатель, а от типа объекта, на который указывает этот указатель.Тип данных (класс), содержащий хотя бы одну виртуальную функцию, называется полиморфным типом (классом), а объект этого типа – полиморфнымобъектом.Таким образом, при вызове виртуальной функции через указатель на полиморфный объект осуществляется динамический выбор тела функции в зависимости от текущего тела объекта, а не от типа указателя.
Тело функции в такомслучае выбирается на этапе выполнения, а не компиляции. В этом и проявляетсядинамический полиморфизм.Замечание. В языке C++ виртуальные методы классов существуют наряду сневиртуальными методами. В некоторых объектно-ориентированных языкахпрограммирования, например, в языке Java, все методы в иерархиях классов являются виртуальными.Виртуальная функция объявляется описателем virtual.
Во всех классах-наследниках наследуемая виртуальная функция остается таковой (виртуальной). Таким образом, все типы-наследники полиморфного типа являются полиморфными типами.Пример:#include <iostream>using namespace std;class A {public:virtual void f (int x) {cout << "A::f" << '\n';}};class C: public A{public:void f (int x) {cout << "C::f" << '\n';}};int main(){A a1;A* pa;C c1;72C* pc;pc = & c1;pc -> f (1);pa = pc;pa -> f (1);pc = (C*) & a1;pc -> f (1);return 0;// C::f// C::f// A::f}11.1.Виртуальные деструкторыВиртуальный деструктор – важная часть аппарата динамического полиморфизма. Дело в том, что, если указатель типа базового класса указывает наобъект производного класса, то при удалении объекта с использованием данногоуказателя в случае невиртуальности деструкторов сработает деструктор того типа,который был использован при объявлении указателя. При описании конструкторов и деструкторов уже было указано, что при удалении объекта во вторую очередь срабатывает деструктор базового типа, удаляя информационные члены базового типа, унаследованные в типе-наследнике, а сначала срабатывает деструктор текущего объекта, удаляя дополнительные члены типа-наследника.
Такимобразом, деструктор базового типа применяется к объектам производного типа.Но при его локальном срабатывании не будут удалены дополнительные членытипа-наследника.Таким образом, при создании и удалении объектов производных типов сиспользованием указателей необходимо описывать деструкторы как виртуальные, если типы-наследники в своем составе имеют динамические структуры.
Приэтом:1) Виртуальный деструктор необходим и для объекта без динамическихструктур в случае наличия динамических структур у типа-наследника,так как деструктор, автоматически генерируемый системой по умолчанию, является невиртуальным.2) Несмотря на то, что имя деструктора производного класса отличается отимени деструктора базового класса, достаточно объявления деструкторавиртуальным только в базовом классе.3) Конструктор, в отличие от деструктора, нельзя описывать как виртуальный, так как всегда срабатывает конструктор именно того типа, который используется при создании объекта, и только после созданияобъекта его адрес передается для присвоения указателю.Пример:#include <iostream>using namespace std;class A {int* pa;73};int ia;public:A(int par1 = 10) {ia = par1;pa = new int [par1];cout << "A() ";}virtual ~A () {delete [] pa;cout << "~A() ";}virtual int sa () {return ia;}class C: public A{double* pc;int ic;public:C (int par2 = 10, int par1 = 10): A (par1) {ic = par2;pc = new double [par2];cout << "C() ";}~ C () {delete [] pc;cout << "~C() ";}virtual int sc () {return ic;}};int main(){A* pp1 = new C( 5);// .
. .cout << '\n' << "size int = " << pp1 -> sa ();cout << "size double = ";cout << ((C*) pp1) -> sc () << '\n';delete pp1;return 0;}Примечание. Комментарий по работе программы.1) В начале работы создается объект типа класса C (при этом через объявленныйуказатель будут доступны только члены, унаследованные от базового класса А). Присоздании объекта сначала срабатывает конструктор базового класса, создавая целочисленный массив. Так как для этого массива фактического параметра нет, то в качестве74размера массива берется значение по умолчанию, заданное в конструкторе базовогокласса.
Затем срабатывает конструктор класса С, создавая дополнительный вещественный массив.2) В конце работы удаляется созданный объект. Так как деструктор базового классаобъявлен виртуальным, то сначала срабатывает деструктор текущего объекта, удаляявещественный массив, а затем – деструктор базового класса, удаляя целочисленныймассив. В результате работы данной программы будет выдана следующая информация:A()C()size int = 10~C()size double = 5~A()3) Если бы деструктор базового класса не был объявлен виртуальным, то при удалении объекта в соответствии с типом указателя pp1 для объекта был бы вызван толькодеструктор базового класса.
В результате вещественный массив остался бы неудаленным, и была бы выдана информация:A()C()size int = 10size double = 5~A()4) Несмотря на то, что метод sc() в классе C является виртуальным, он недоступен напрямую через указатель pp1, так как этого метода нет в структуре класса A. Поэтому для вызова этого метода для созданного объекта через указатель pp1 требуетсяпреобразование:((C*)pp1)->sc()С другой стороны, если бы в классе С был описан метод с прототипом: int sa(),то он был бы виртуальным и по операции pp1 -> sa() сработал бы его алгоритм, а неалгоритм метода sa(), объявленного в базовом классе.11.2.Реализация виртуальных функцийДля реализации механизма виртуальных функций используется специальный, связанный с полиморфным типом, массив указателей на виртуальные методы класса.
Такой массив называется Таблицей Виртуальных Методов (ТВМ).В каждый полиморфный объект компилятор неявно помещает указатель, условнообозначаемый какvtbl* pvtbl ,на соответствующую ТВМ, хранящую адреса виртуальных методов.Для указанного выше примера с виртуальными функциями будут созданыследующие структуры:75В ТВМ типа-наследника имеющиеся адреса одинаковых методов замещаются, а новые – дописываются в конец. Так, если бы в классе С был описан метод спрототипом int sa(), то ТВМ для класса С имела бы вид:Так как указатель на ТВМ находится в самом начале объекта, то он доступенвсегда, каким бы ни был тип указателя на объект. Конечно, при этом из ТВМмогут быть выбраны только те методы, которые имеются в структуре указателя(входят в так называемый интерфейс), То есть, как показано в примере, еслиобъект производного типа обрабатывается через указатель базового типа, то изТВМ данного объекта можно вызывать только виртуальные методы, перечисленные в базовом типе.
Естественно, при этом будет выполняться алгоритм, определенный для данного объекта в соответствии с его типом.Издержками при использовании виртуальных функций является дополнительная память для неявного хранения указателя на ТВМ в каждом полиморфномобъекте.7611.3.Абстрактные классы. Чистые виртуальные функцииЧистая виртуальная функция – функция вида:virtualформальныеИмяТип_возвращаемого(параметры ) = 0;функциизначенияТакая форма записи функции означает, что данная функция (точнее – методкласса) не имеет тела, описывающего ее алгоритм.Абстрактный класс – это класс, содержащий хотя бы одну чистую виртуальную функцию.Нельзя создавать объекты на основе абстрактных классов, так как последние,имея в своем составе чистые виртуальные функции, не являются полноценнымитипами данных.