Динамическая идентификация типов




Пример 1

Такой традиционный способ приведения типов проектировался без учета объектно-ориентированного программирования. Поэтому в язык C++ помимо оператора круглых скобок было введено еще четыре оператора приведения типа: dynamic_cast, const_cast, reinterpret_cast, static_cast, которые являются частными случаями оператора круглых скобок.

Кроме стандартной формы в стиле С, язык С++ под­держивает дополнительные операторы приведения типа:

 

static_cast( Дейтел, гл 5 )

dynamic_cast( Дейтел, гл 13 )

reinterpret_cast( Дейтел, гл 17 )

const_cast (Дейтел, гл 23)

 

static_cast выполняет неполиморфное приведение типов. Его можно использовать для любого стандартного преобразования. При этом никакие проверки во вре­мя работы программы не выполняются. Фактически, static_cast — это аналог стандартной операции пре­образования в стиле С.

 

Пример 2

static_cast<double>(i) создает временную копию с плавающей точкой своего операнда i (в круглых скобках). Подобное применение операции приведения называется явным преобразованием. Значение, сохраняемое в i, остается целым. Операции приведения к типу возможны для любых типов данных и для классовых типов. Операция static cast образуется из ключевого слова static_cast, за которым следует имя типа данных, заключенное в угловые скобки (< и >). Приведение типа является унарной (одноместной) операцией т.е. операцией, имеющей всего один операнд.

Оператор dynamic_cast предназначен для полиморфного приведения типа, т. е. для приведения указателя или ссылки на базовый класс к указателю или ссылке на производный класс, если такое преобразование допустимо.

 

dynamic_cast перед присваиванием проверяет законность выполнения задан­ной операции приведения типа. Если такую операцию выполнить нельзя, то выражение устанавливается рав­ным нулю. Например, если даны два полиморфных класса, B и D, причем класс D выве­ден из класса B, то оператор dynamic_cast всегда может преобразовать указатель D* в указатель B*. Оператор dynamic_cast может преобразовать указатель B* в указа­тель D* только в том случае, если адресуемым объектом является объект D.

Операция dynamic_cast проверяет тип объекта, на который ссылается указатель, и определяет, находится ли этот тип в отношении является с типом, к которому преобразуется указатель. Если имеет место отношение является, dynamic_cast возвращает адрес объекта. В противном случае возвращается 0.

 

Пример 3

Строка 42 это естественно, можно даже обойтись без dynamic_cast

Указателю базового класса присваивается адрес объекта базового

Строка 56 это естественно,, можно даже обойтись без dynamic_cast

Указателю производного класса присваивается адрес объекта производного

 

Строка 70 это безопасно, можно даже обойтись без dynamic_cast

Указателю базового класса присваивается адрес объекта производного,

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

базового класса.

 

Строка 86

Указателю производного класса присваивается адрес базового объекта.

Прямое присваивание привело бы к ошибке компиляции.(строка 87)

Это незаконно. Отношение является имеет место в направлении от производного класса к базовым классам, но не наоборот. Объект базового класса не содержит элементов, имеющихся только в производном классе и которые можно было бы вызвать через указатель базового класса и dynamic_cast вернет 0. Если бы мы использовали простое приведение как в строке 88, присвоение бы произошло, но попытка вызвать ф-ию производного класса в строке 94 используя адрес базового объекта, привела бы к аварийному завершению программы.

 

Следует отметить, что при использовании круглых скобок преобразование осуществляется в любом случае, приводит ли это к выходу за границы объекта или нет.

 

 

reinterpret_cast переводит один тип в совершенно другой.Например, его можно использовать для перевода указа­теля в целый тип и наоборот. Оператор reinterpret_cast следует использовать для перевода типов указателей, которые несовместимы по своей природе.

Пример 4

Пример 5

Программа для обработки кредита. Рассмотрим следующую формулировку задачи:

Создать для компании, которая может иметь до 100 клиентов, программу для обработки кредита, которая способна хранить до 100 записей фиксированной длины. Каждая запись должна состоять из полей номера счета (служащего ключом записи), фамилии, имени и баланса счета. Программа должна обеспечивать обновление счета, вставку нового счета, удаление счета и вывод записей для всех счетов в форматированный текстовый файл для распечатки.

 

Пример иллюстрирует открытие файла произвольного доступа, формат записи которого определяется объектом класса ClientData, и запись данных на диск в двоичном формате. Программа инициализирует все 100 записей файла credit.dat пустыми объектами, используя функцию write. Каждый пустой объект содержит 0 для номера счета, нулевые строки для фамилии и имени (представленные пустыми кавычками) и 0.0 для баланса. Под каждую запись отводится пустое пространство, в котором будут храниться данные счета.

Объекты класса string не имеют единообразного размера, поскольку они используют динамически распределяемую память, чтобы хранить строки различной длины. Эта программа должна работать с записями постоянной длины, поэтому класс ClientData сохраняет имя и фамилию клиента в символьных массивах фиксированного размера. Элемент-функции setLastName (строки 37-45) и setFirstName (строки 54-62) копируют символы из объекта string в соответствующие символьные массивы. Рассмотрим функцию setLastName. Строка 40 инициализирует const char *lastNameValue результатом вызова функции data класса string, которая возвращает массив, содержащий символы строки. [Замечание. Этот массив не обязательно ограничен нулем.] Строка 41 вызывает функцию size класса string, чтобы получить длину lastNameString. Строка 42 гарантирует, что длина строки (length) будет не более 15 символов, после чего строка 43 копирует length символов из lastNameValue в символьный массив lastName. Элемент-функция setFirstName производит те же действия для имени клиента.

Строка 18 создает объект ofstream для файла credit.dat. Второй аргумент конструктора — ios::binary — указывает, что мы открываем файл для вывода в двоичном режиме, который необходим, если нам нужно записывать записи фиксированной длины. Строки 31-32 записывают пустые объекты blankClient в файл credit.dat, ассоциированный с объектом потока оutCredit. Как вы помните, операция sizeof возвращает размер в байтах объекта, заключенного в скобки. Первый аргумент функции write в строке 31 должен быть типа const char *. Но &blankClient имеет тип ClientData *. Чтобы преобразовать &blankClient в const char *, в строке 31 используется операция приведения reinterpret_cast, иначе компиляция вызова функции write приводила бы к ошибке.

Операция reinterpret_cast производится во время компиляции и не изменяет значение объекта, на который указывает ее операнд. Вместо этого она просит компилятор интерпретировать операнд как целевой тип (указанный в угловых скобках после ключевого слова reinterpret_cast). В примере мы применяем reinterpret_cast, чтобы преобразовать указатель на ClientData в const char *, в результате чего объект ClientData интерпретируется как массив байтов, который выводится в файл.

 

 

const_cast используется для явного переопределения модификаторов const и/или volatile. Новый тип дол­жен совпадать с исходным типом, за исключением изменения его атрибутов const или volatile. Чаще всего оператор const_cast используется для снятия атрибута const.

Пример 6

 

Вообще говоря, применение операции const_cast является небезопасным, поскольку позволяет программе модифицировать переменную, объявленную как const и не подлежащую модификации. Но существуют ситуации, когда желательно или даже необходимо отменить константность. Например, в старые библиотеки С и C++ могут входить функции с не-константными параметрами, не модифицирующие эти параметры. Если вы хотите передать такой функции константные данные, вам нужно отменить их константность; в противном случае компилятор будет выдавать сообщения об ошибках.

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

В этой программе функция maximum (строки 11-14) принимает две строки в стиле С как параметры типа const char * и возвращает const char *, указывающий на большую из двух строк. Функция main объявляет две строки в стиле С как не-константные символьные массивы (строки 18-19); эти массивы, таким образом, модифицируемы. Мы хотим вывести в main большую из двух строк, а затем преобразовать ее буквы в верхний регистр.

Два параметра функции maximum имеют тип const char *, поэтому ее возвращаемый тип также должен быть объявлен как const char *. Если специфицировать возвращаемый тип просто как char *, компилятор выдаст сообщение об ошибке, указывающее, что возвращаемое значение не может быть преобразовано из const char * в char * — это опасное преобразование, поскольку онопытается рассматривать данные, которые функция считает константными, в качестве не-константных.

Но хотя функция maximum считает данные константными, мы знаем, что исходные массивы в main не содержат константных данных. Следовательно, main в случае необходимости должна иметь возможность модифицировать содержимое этих массивов. Поскольку мы знаем, что массивы модифицируемы, мы применяем const_cast (строка 23) для отмены константности указателя, возвращаемого функцией maximum, чтобы впоследствии мы могли модифицировать данные в массиве, представляющем большую из двух строк. Затем в цикле for (строки 27-28) мы используем этот указатель в качестве имени массива для преобразования содержимого большей строки в буквы верхнего регистра. Без const_cast в строке 23 эта программа компилироваться не будет, так как присваивание указателя типа const char * указателю типа char * не допускается.

 

Динамическая идентификация типов

 

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

ссылается указатель.

Для определения типа объекта применяется оператор typeid(), который допускается использовать после включения в программу заголовочного файла<typeinfo>. Оператор typeid() принимает объект object, тип которого следует получить, и имеет следующий синтаксис:

 

typeid(object)

 

Оператор возвращает ссылку на объект класса type_info, который описывает тип объекта object. Среди открытых методов класса type_info имеется оператор сравнения ==, оператор неравенства!= и метод name(), который возвращает указатель на имя класса объекта object.

 

Пример 8

Как видно из листинга метод typeid() может работать как с базовыми типами, так и с объектами классов, созданных программистом. Если оператору typeid() передается указатель на полиморфный класс (т. е. класс, который содержит хотя бы одну виртуальную функцию), то он возвращает класс реального объекта, на который указывает указатель

 

Пример 9

Если базовый класс base не является полиморфным (т. е. не содержит виртуальных функций), то даже в том случае, когда указатель базового типа ссылается на объект производного типа, оператор typeid() возвратит тип базового типа. Например, убрав ключевое слово virtual из определения метода print() базового класса base, можно получить следующий результат выполнения программы:

 

fst = base

snd = base



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2018-09-18 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: