cpp-oop (823968), страница 12
Текст из файла (страница 12)
Этапроблема решается применением виртуального деструктора. При объявлении деструкторабазового класса виртуальным деструкторы производных классов также становятсявиртуальными. Тогда при уничтожении объекта с помощью оператора delete вызовдеструктора будет происходить через ТВМ и, следовательно, деструкторы производныхклассов будут вызваны корректно.Обе указанные особенности имеют место и при работе с полиморфными объектами.Пример 3.4.
Использование указателей на базовый класс и виртуальногодеструктора. В программе определены два класса: класс, содержащий поле целого типа, ипроизводный от него класс, содержащий в качестве поля динамический массив, размеркоторого определяется значением поля целого типа, унаследованного от родителя.#include <locale.h>#include <iostream>#include <stdlib.h>#include <time.h>using namespace std;class integ{protected: int n;public:ОглавлениеИванова Г.С., Ничушкина Т.Н.
«Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»82integ(int vn) { n=vn; } // конструктор// деструкторvirtual ~integ() {}virtual void print(void) { cout<<""<<n<<endl;}};class masinteg: public integ{int *mas;public:masinteg(int vn);// конструктор~masinteg(){ delete [] mas; } // деструкторvoid print(void);};masinteg::masinteg(int vn):integ(vn){mas=new int[n];for(int i=0;i<n;i++)mas[i]=rand()/1000;}void masinteg::print(){for(int i=0;i<n;i++){cout<<"" <<mas[i];cout<< endl; }}void main(){setlocale(0,"russian");srand( (unsigned)time( NULL ) );// указатель на базовый классinteg *pa;pa=new integ(5); // создание объекта базового классаpa->print();delete pa;// автоматический вызов деструктора integpa=new masinteg(6); // создание объекта производного классаpa->print();delete pa;// автоматический вызов деструктора masintegsystem("pause");}ОглавлениеИванова Г.С., Ничушкина Т.Н.
«Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»833.4Восходящее и нисходящее изменение типа объектовВ ООП при работе с объектами возможно временное изменение их типа. При этомразличают:• нисходящее приведение типа;• восходящее приведение типа.Приведение типа объекта называют нисходящим, если в его результате указатель илиссылка на объекты базового класса преобразуется в указатель или ссылку на объектыпроизводного, и восходящим, если указатель или ссылка на объекты производного классапреобразуется в указатель или ссылку на объекты базового класса.Восходящее приведение типа возможно всегда, поскольку все поля и методыпредка унаследованы потомком, а, следовательно, ошибок при обращении к компонентампри восходящем приведении возникать не будет.
Данный тип приведения типа объектаиспользуют при необходимости вызвать переопределенный в потомке метод базовогокласса, например:class A {public:void func(char ch);};class B : public A {public:void func(char *str);};...B b;b.func("c");// вызвать B::func()(A)b.func('c');// вызвать A::func(); (A)b - восходящее приведение типаНисходящее приведение приведение допустимо, только для случаев, когда объектпроизводного класса доступен через указатель или ссылку на объект базового, т.е.
когдапри приведении восстанавливается соответствие объекта своему классу (или при сложнойОглавлениеИванова Г.С., Ничушкина Т.Н. «Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»84иерархии – возможно его родителю, если указатель или ссылка принадлежали более«древним» предкам).Нисходящее приведение выполняют, чтобы получить через указатель или ссылкубазового класса доступ к компонентам, добавленным в производном классе. Такаяситуация встречается сравнительно часто при работе с полиморфными объектами.При выполнении нисходящего приведения типов необходима проверка, так какникакой гарантии, что указатель ссылается на адрес объекта именно данногопроизводного класса, нет.
Используется же это преобразование при работе сполиморфными объектами постоянно, в связи с тем, что это единственный способобеспечить видимость полей производного класса при работе с объектом через указательна базовый класс.Операторы приведения типов. В последних версиях С++ приведение типоввыполняется, как традиционно, так и с использованием специальных операторов.Рассмотрим целесообразность использования всех возможных вариантов для нисходящегоприведения типов:1) (<Тип>)<Переменная> – используется в Си/С++ для любых типов, ничего непроверяет;2) static_cast <Тип>(<Переменная>) – используется в С++ для любых типов,ничего не проверяет;3) reinterpret_cast <Тип указателя>(<Указатель или интегральный тип>) – используется в С++ дляуказателей, ничего не проверяет;4) dynamic_cast <Тип указателя на объект>(<Указатель на объект>) – используется в С++ только для полиморфныхклассов, требует указания опции компилятора /GR (Project/Settings…) в Visual Studio, еслиприведение невозможно, то возвращает NULL.Следовательно для нисходящего приведения типов полиморфных объектовцелесообразно применять именно dynamic_cast.Рассмотрим эффект использования различных вариантов приведения типов болееподробно.Динамическоеп р и в е д е н и е типа: dynamic_cast <T>(t).Операнды:T – указатель или ссылка на класс или void*,ОглавлениеИванова Г.С., Ничушкина Т.Н.
«Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»85t – выражения типа указателя, причем оба операнда либо указатели, либо ссылки.Приведение типа осуществляется во время выполнения программы. Предусмотренапроверка возможности преобразования, использующая RTTI (информацию о типепеременных, используемую во времени выполнения программы), которая строится в С++только для полиморфных объектов.Применяется для нисходящего приведения типов полиморфных объектов, например:#include <iostream>class A{virtual ~A(){}/* класс обязательно должен включатьpublic:виртуальный метод, так как для выполненияприведения требуется RTTI*/};class B: public A{public:virtual ~B(){}};void func(A& a) /* функция, работающая с полиморфным объектом*/{B& b=dynamic_cast<B&>(a); // нисходящее приведение типов}void main(){B b;func(b);//вызов функции с полиморфным объектомsystem("pause");}Если вызов dynamic_cast осуществляется в условной конструкции, то ошибкапреобразования, обнаруженная на этапе выполнения программы, приводит к установкезначения указателя равным NULL, в результате чего активизируется ветвь «иначе».Например:if (Derived* q=dynamic_cast<Derived*> (p)ОглавлениеИванова Г.С., Ничушкина Т.Н.
«Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»86{<если преобразование успешно, то ...>}else {<если преобразование неуспешно, то ...>}В этом примере осуществляется преобразование указателя на объекты базовогокласса в указатель на объекты производного класса с проверкой правильности по RTTI наэтапе выполнения программы. Если преобразование некорректно, то оператор возвращаетNULL, и устанавливается q=NULL, в результате чего управление передается на ветвьelse.Если вызов осуществляется в операторе присваивания, то при неудаче генерируетсяисключение bad_cast.
Например:Derived* q=dynamic_cast<Derived*> (p);С т а т и ч е с к о е п р и в е д е н и е типа: static_cast<T>(t).Операнды: T – указатель, ссылка, арифметический тип или перечисление; t –аргумент типа, соответствующего T. Оба операнда должны быть определены на этапекомпиляции. Операция выполняется на этапе компиляции без проверки правильностипреобразования.Может преобразовывать:1) целое число в целое другого типа или в вещественное и обратно:int i;float f=static_cast<float>(i); /* осуществляет преобразованиебез проверки на этапе компиляции программы */2) указатели различных типов, например:int *q=static_cast<int>(malloc(100)); /* осуществляет преобразованиебез прове рки на этапе компиляции программы */3) указатели и ссылки на объекты иерархии в указатели и ссылки на другие объектытой же иерархии, если выполняемое приведение однозначно.
Например, восходящееприведение типов или нисходящее приведение типов неполиморфных объектов, иерархииклассов которых не используют виртуального наследования:ОглавлениеИванова Г.С., Ничушкина Т.Н. «Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»87// класс не включает виртуальных функцийclass A {…};class B:public A{};// не используется виртуальное наследованиеvoid somefunc(){Aa;Bb;B& ab=static_cast<B&>(a); // нисходящее приведениеA& ba=static_cast<A&>(b); // восходящее приведение}Остальные два оператора приведения типа напрямую с объектами обычно неиспользуются. Это оператор const_cast<T>(t), который применяют для отменыдействия модификаторов const или volatile, и оператор reinterpret<T>(t),который осуществляет преобразования, ответственность за которые полностью ложитсяна программиста.Пример3.5.Восходящееинисходящееприведениетиповобъектовиспользованием различных средств языка.#include <iostream>#include <string.h>using namespace std;class TA{protected:char c;public:TA(char ac):c(ac){}virtual void func(){cout<<c<<endl;}};class TB:public TA{char S[10];public:TB(char *aS):TA(aS[0]){strcpy(S,aS);}void func(){cout<<c<<' '<<S<<endl;}};void main()ОглавлениеИванова Г.С., Ничушкина Т.Н.
«Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»с88{TA *pA=new TA('A'),*pC=new TB("AB");TB *pB=new TB("AC");// восходящее приведение типов((TA *)pB)->func();reinterpret_cast<TA *>(pB)->func();static_cast<TA *>(pB)->func();dynamic_cast<TA *>(pB)->func();// нисходящее приведение типов - корректно((TB *)pC)->func();reinterpret_cast<TB *>(pC)->func();static_cast<TB *>(pC)->func();dynamic_cast<TB *>(pC)->func();// нисходящее приведение типов - некорректно((TB *)pA)->func();reinterpret_cast<TB *>(pA)->func();static_cast<TB *>(pA)->func();// dynamic_cast<TB *>(pA)->func();// фиксирует ошибку!// проверка корректности нисходящего преобразованияif (TB *pD=dynamic_cast<TB *>(pA)) pD->func();else cout<<"Cast Error"<<endl;system("pause");}ОглавлениеИванова Г.С., Ничушкина Т.Н.
«Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»893.5Контейнерные классыРешение многих задач подразумевает создание наборов объектов и обработку такихнаборов. Объект, назначением которого является хранение объектов других типов иуправление ими, принято называть контейнером. Соответственно класс такого объектаназывают контейнерным.При реализации контейнерного класса используют механизмы композиции илинаполнения. Если контейнерный класс реализует механизм композиции, то тип иколичество управляемых объектов жестко определены типом и количеством объектныхполей.