А.В. Столяров - Введение в язык Си++ (1114949), страница 23
Текст из файла (страница 23)
Ясно, что множество частных случаев представляет собой подмножество множества случаев общих. Получается, что втерминах множеств наследование описывается отношением включения(A D Б). Естественным следствием такого рассмотрения является термин подкласс (англ, subclass) для обозначения порождённого класса итермин надкласс или суперкласс (англ, su p e rclass) — для обозначения базового.Такая терминология часто порождает определённую путаницу. Делотут в том, что объект порождённого класса ( подкласса ) мало того, чтопамяти занимает заведомо не меньше (а при добавлении новых полей —и больше), нежели объект класса базового ( суперкласса ), но ещё и содерж и т в себе о б ъ е к т базового класса, т.
е. объект суперкласса оказывается п о д об ъекто м объекта подкласса. Получается, что мы рассматриваемодновременно два отношения вложенности, причём они мало того чторазные, они оказываются направлены п р о т и в о п о л о ж н о : объект базово106го класса вложен в объект порождённого класса (чисто технически), авот сами классы «вложены», наоборот, порождённый в базовый.Вся эта путаница обусловлена исключительно применением двух разных терминологических систем в одном месте. Когда речь идёт о суперклассах и подклассах — это значит, что используется теоретико-множественная терминология. Когда же речь заходит об используемой памяти иподобъектах — очевидно, что разговор идёт в терминах реализаторской(прагматичной) точки зрения.
Так или иначе, обе терминологические системы используются и имеют право на существование; вы на практикеможете столкнуться как с одним вариантом терминологии, так и с другим, а в некоторых случаях — и с их смешением, как в вышеприведённомпримере. Поэтому желательно понимать, что означают термины обеихсистем.§4.12. О перации п ри вед ен и я ти п аВ процессе программирования часто приходится изменять тип выражения. Иногда это делается неявно, как, например, в случае сложенияцелочисленного значения со значением дробным (с плавающей точкой).В иных случаях (как, например, при изменении типа указателя) приходится явно указывать компилятору новый тип выражения.В языке Си это делалось с помощью операции преобразованият и п а , записываемой как унарная операция, символ которой есть имятипа, заключенное в круглые скобки, как, например, в следующем выражении:char *р = (ch ar*)m allo c(100);Здесь значение выражения malloc (100), имеющее тип void *, приводитсяк типу char*.Операция приведения типа оп асна в том смысле, что её применениепозволяет при желании обойти любые ограничения, вводимые системойтипизации, включая, например, запреты на запись в константные области памяти и даже защиту данных в классах.
Необдуманное применениепреобразования типов приводит к запутыванию программы и, в конечном счёте, к трудно выявляемым ошибкам.Для снижения негативного эффекта операции приведения типов, атакже для поддержки полиморфного программирования в языке С и + +вводятся четыре дополнительные операции, предназначенные для преобразования типа выражения.
Эти операции имеют достаточно нетривиальный синтаксис: сначала записывается ключевое слово, задающее операцию (s ta tic _ c a s t, dynamic_cast, con st_cast или re in te rp re t_ c a st),затем в угловых скобках ставится имя нового типа и, наконец, в круглых107скобках записывается само выражение, тип которого необходимо изменить, например:Square *sp = s ta tic _ c a st< S q u a r e *> (sc e n e [i]) ;В отличие от операции приведения типов в языке Си, которая применялась для всех случаев смены типа, каждая из операций С и + + предназначена для своего случая.
Так, операция con st_cast позволяет снятьили, наоборот, установить сколько угодно модификаторов const6; попытка сделать с её помощью любое другое изменение типа вызовет ошибкупри компиляции:in t * р ;const in t *q ;const char * s ;// ...q = p; / / можно без преобразованияp = q; / / ошибка! снятие constp = c o n st_ c a st< in t*> (q ); / / правильноp = c o n st_ c a st< in t*> (s) ; / / ошибка!Отметим, что наличие в языке операции co n st_cast не отменяет опасности такого преобразования. Реальная потребность в обходе константнойзащиты возникает крайне редко; прежде чем применять преобразование, подумайте, всё ли вы правильно делаете, не забыли ли вы, например, пометить словом const функцию-метод, не изменяющую состояниеобъекта, и т. п.
Д л я применения операции co n st_cast необходимыочень веские причины, и сакрам ентальное «б ез неё не рабо тает»такой причиной не является. В некоторых программистских коллективах на каждое применение co n st_cast необходимо личное разрешениеруководителя разработки.Операция s t a t ic _ c a s t предназначена для работы с наследуемымиобъектами и позволяет преобразовать указатель или ссылку в направлении, противоположном закону полиморфизма, т.
е. от базового класса кпорождённому. Попытка произвести любое другое преобразование вызовет ошибку. Приведём примеры:c la s s А { / * . . . * / } ;c la s s В pu blic А { / * .. .. * / } ;c la s s С { / * . . . * / } ;А *ар: »В *Ър:»С *ср: »6Также эта операция работает с модификатором v o la tile , который мы в нашемкурсе не рассматриваем.108//арЪрbpср...= bp; / / можно без преобразования= а р ; / / ошибка!= sta tic _ c a s t< B *> (а р ); / / допустимо= s ta tic _ c a s t< C *> (а р ); / / ошибка!Отметим, что делать это следует только в том случае, если мы действительно уверены, что по данному адресу расположен объект именно тоготипа, к которому мы намерены преобразовывать; иное в большинствеслучаев приведёт к аварии.Операция r e in te rp re t_ c a st позволяет произвести любое преобразование (чего угодно во что угодно), если только компилятор понимает, какэто сделать (в частности, преобразовать объекты структур разных типовдруг к другу не получится, поскольку непонятно, как такое преобразование производить).
Фактически эта операция эквивалентна операцииязыка Си, которая обозначается именем типа, взятым в круглые скобки.Рекомендуется, однако, применять именно r e in te rp re t_ c a st, а не операцию Си, поскольку такие преобразования требуют особого внимания, авыражение с использованием re in te rp re t_ c a st лучше заметно в текстепрограммы, чем имя типа в скобках.Несколько особое место занимает операция dynamic_cast. Три операции, которые мы рассмотрели выше, служат для управления системойконтроля типов, т. е. для управления компилятором, и не порождаютдействий, осуществляемых во время исполнения программы7.
Операцияdynamic_cast, в отличие от остальных, предполагает проведение нетривиальной проверки во время исполнения программы. Подобно операцииs t a t ic _ c a s t , операция dynamic_cast предназначена для преобразованияадресов объектов в направлении, противоположном закону полиморфизма, т. е. от адреса предка к адресу потомка. В случае со s t a t ic _ c a s t ответственность за корректность такой операции возлагается на программиста: именно программист тем или иным способом должен проверить,что преобразуемый адрес (будь то указатель или ссылка) указывает наобъект нужного типа. Если же применить dynamic_cast, то она сама проведёт необходимую проверку и в случае, если преобразование некорректно (то есть по заданному адресу в памяти не находится объект нужноготипа), вернёт нулевой указатель.
Проверка, таким образом, осуществляется во время исполнения, т. е. динамически, отсюда название операции.Проверка типа производится на основании значения указателя на таблицу виртуальных функций; дело в том, что такая таблица уникальна7Кроме преобразования между целыми числами и числами с плавающей точкой,что требует неких действий; иногда бывает необходимо изменить и численное значениеуказателя, но в нашем курсе не рассматриваются механизмы, порождающие такуюнеобходимость.109для каждого класса, имеющего виртуальные методы, т.
е. её адрес однозначно идентифицирует класс объекта. Таким образом, dynamic_castможет работать только с классами (или структурами), имеющими виртуальные функции. В некоторых источниках такие классы называют полиморф ными, что не совсем корректно: как мы видели, полиморфизм вопределённом смысле работает и для классов, не имеющих виртуальныхфункций.Отметим ещё один немаловажный момент.
Обычно реализацииdynamic_cast весьма неэффективны по времени исполнения, т. е. работают очень медленно. Поэтому злоупотреблять ими не следует.§4.13. Е щ ё о п реобразован и и типов вобработчи ках исклю ченийПри обсуждении преобразований типов выражений в обрабочиках исключительных ситуаций (см. стр. 84) мы отметили, что одним из наиболее важных видов преобразования является преобразование по законуполиморфизма, однако подробное обсуждение этого отложили, поскольку на тот момент ещё не было введено наследование.Возвращаясь к этому вопросу, заметим, что третий и последний виддопустимых преобразований от типа выражения в операторе throw к типу, указанному в заголовке catch — это преобразование адреса (т.е.
указателя или ссылки) объекта-потомка к соответствующему адресному типу объекта-предка. Таким образом, если мы опишем два класса, причёмодин унаследуем от другого:c la s s А { / * . . . * / } ;c la s s В : pu blic А { / * . . . * / } ;то обработчик видаcatch(const А& ex) { / * . . . * / }сможет обрабатывать исключения обоих типов, т.
е. результат как оператора throw А( . . . ) ;, так и throw В( . . . ) ; .Это свойство используется для создания иерархий исключительныхситуаций. Например, мы можем поделить все ошибки, возникающие вкакой-либо программе или библиотеке, на следующие категории:• ошибки, возникающие по вине пользователя:— синтаксические ошибки при вводе (например, буквы там, гдеожидается число);— неправильно указано имя файла;110— неправильно введённый пароль;— недопустимая комбинация требований (например, одновременное требование упорядочивания по возрастанию и по убыванию);• ошибки, обусловленные средой выполнения:— переполнение диска;— отсутствие файлов, необходимых для работы;— прочие ошибки операций ввода-вывода;— недостаток оперативной памяти;— ошибки при работе с сетью;— и т.