А.В. Столяров - Введение в язык Си++ (1114949), страница 18
Текст из файла (страница 18)
Мы, однако, ограничилисьв примере одной строковой константой, чтобы не формировать строку,содержащую всю перечисленную информацию — ведь это потребовалобы значительного увеличения кода и создало бы определённые трудности с освобождением памяти от такой строки, поскольку саму строкупришлось бы создавать динамически.Кроме того, есть и ещё одна проблема. Программа может использовать несколько библиотек, причём изготовленных разными производителями; также программа может быть разделена на несколько подсистем,создаваемых более или менее независимо. В такой ситуации весьма желательно иметь некий универсальный способ разделения исключений попризнаку их возникновения в той или иной подсистеме программы илибиблиотеке. Встроенные типы для этого не подходят совсем, поскольку идея использования тех же текстовых строк в качестве исключенийможет прийти в голову одновременно авторам сразу всех используемыхнами библиотек и половины наших подсистем.Именно поэтому чаще всего в качестве типа исключения выступаетспециально для этой цели описанный класс, а значением исключения —соответственно, объект такого класса.
С одной стороны, в объект класса80можно поместить всю информацию, какая нам только может показатьсяполезной для обработчика; действительно, никто ведь не мешает описать в классе столько полей, сколько нам захочется. С другой стороны,каждая подсистема и каждая библиотека в этой ситуации, скорее всего,введёт свои собственные классы для представления исключений. В головной программе мы сможем обрабатывать такие классы отдельнымиобработчиками, что позволит разделить обработку исключений, сгенерированных в разных подсистемах программы.Отметим один очень важный момент.
Как правило, выбрасываемыйв качестве исключения объект создаётся локально. Локальные объекты, естественно, исчезают вместе со стековым фреймом создавшей ихфункции; таким образом, тот объект, который «поймается» в обработчике исключения, заведомо не может быть тем же экземпляром объекта, который фигурировал в операторе throw, и является, как нетруднодогадаться, его копией. Поэтому практически всегда в классе, используем ом д л я исклю чений, обязан п ри сутствовать конструктор копирования.Для примера опишем класс, объект которого было бы удобно использовать в качестве исключения в нашей функции line_count_in_f i l e ( ) .Объект класса будет хранить имя файла, текстовый комментарий (этоткомментарий поможет понять, в каком месте программы произошлаошибка) и будет запоминать значение глобальной переменной errno намомент создания объекта.
Для копирования строк мы опишем в приватной части класса вспомогательную функцию strdup4; поскольку доступк объекту ей не нужен, опишем её с квалификатором s t a t ic .c la s s FileE xception {char *filen am e;char * comment;in t err_code;p u b lic :FileE xcep tion (con st char *fn , const char *cm t);FileE xcep tion (con st FileException& o th e r);"FileE xception Q ;const char *GetNameQ const { return filenam e; }const char *GetComment0 const { return comment; }in t GetErrnoQ const { return err_code; }p r iv a te :s t a t i c char *strd u p (co n st char * s t r ) ;>;4 Эта функция будет полностью аналогична одноимённой функции из стандартнойбиблиотеки Си, но будет использовать для выделения памяти new[], а не mallocO.81Описания конструкторов, деструктора и функции strdup вынесем запределы заголовка класса:F ile E x cep tio n ::F ile E x ce p tio n (co n st char *fn , const char *cmt){filenam e = strd u p (fn );comment = strdup(cm t);err_code = errno;}F ile E x cep tio n ::F ile E x ce p tio n (co n st FileException& other) {filenam e = strd u p (o th er.
file n am e);comment = strd u p (o th er. comment);err_code = o th er,err_code;}F ile E x c e p tio n :: "FileE xception Q {d e le te [] filenam e;d e le te [] comment;}char* F ile E x c e p tio n :: strdup(con st char * s t r ) {char *r e s = n e w [ s tr le n (s tr ) + l] ;str c p y (r e s , s t r ) ;return r e s ;}Теперь мы можем изменить начало функции lin e _ co u n t_ in _ file(см.
стр. 75) на следующее:unsigned in t lin e _ co u n t_ in _ file (co n st char *file_nam e){FILE * f = fopen(file_nam e, " r " ) ;if Of)throw FileE xception (file_nam e,"couldn’ t open fo r lin e coun tin g");// ...Что касается обработки такого исключения, то обработчик (catch-блок)теперь может выглядеть, например, так:catch(const FileE xception &ех) {f p r in t f ( s t d e r r , " F ile exception: %s (% s): %s\n",ex.GetNameO , ex.GetCommentO ,s t r e r r o r (e x .GetErrno( ) ) ) ;return 1;}823.7.
А вто м ати ч еская очи сткаВ примере, рассмотренном на стр. 80, мы были вынуждены перехватывать и потом заново «выбрасывать» исключения произвольного вида,чтобы обеспечить корректное удаление локально выделенной динамической памяти. К счастью, так приходится действовать не всегда. Работа сисключениями изрядно облегчается тем, что ком пилятор гаран тирует, что п реж д е, чем ф ун кц и я заверш и тся (по исклю чению лиили обычным путём ), д л я всех локальн ы х объектов, имею щ ихд еструкторы , эти д еструкторы будут корректно вы зван ы .Пусть, например, имеется класс А, снабженный нетривиальным деструктором, и описана функцияvoid f ( ) {А а;//..g ();//..}В момент завершения работы функции f () для объекта а будет вызвандеструктор, причём произойдёт это даже в том случае, если функция g ()«выбросит» исключение и именно оно станет причиной завершения f ().Это свойство языка С и + + называется а в т о м а т и ч е с к о й о ч и стк о й .§3.8.
П р ео б р азо ван и я типов в обработчи кахисклю ченийТип исключения, указанный в обработчике (catch-блоке), не всегдаполностью совпадает с типом выражения, использованного в оператореthrow, однако правила преобразования типов в данном случае отличаются от правил, действующих при вызове функций. Одно из основныхотличий тут в том, что целочисленные типы при обработке исключенийне преобразуются друг к другу, то есть, например, обработчик с указанным типом исключения in t не поймает исключение типа char илиlong.Допустимые преобразования проще будет явно перечислить.
Вопервых, любой тип может быть преобразован к ссылке на этот тип, тоесть если оператор throw использует выражение типа А, то такое исключение может быть обработано catch-блоком вида catch (A &ref). Вовторых, неконстантные указатели и ссылки могут быть преобразованы кконстантным, то есть, например, если мы бросим исключение типа char*,то оно может быть поймано не только как char*, но и как const char*.83Отметим, что в обратную сторону преобразование запрещено, так что обработчик типа char * (без модификатора const) не может поймать выражение типа const char*, и, в частности, не может обработать исключение, «выброшенное» с использованием строкового литерала, напримертакое:throw "I can’ t work";Последний (и, возможно, самый важный) вид преобразований имеетотношение к наследованию и полиморфизму.
К обсуждению этой темымы вернёмся после рассказа о наследовании.844. Наследование иполиморфизм§4.1. И ер ар х и чески е п редм етн ы е областиОчень часто, и особенно в более-менее крупных компьютерных программах, возникает ситуация, при которой объекты предметной областиестественным образом объединяются в некие категории, причём объектыкаждой категории могут подразделяться на подкатегории и т.
д., образуя, таким образом, иерархию т и п о в о б ъ ек то в. Например, в программе, осуществляющей имитационное моделирование дорожного движения, могут быть объекты категории «участник дорожного движения»,причём эта категория подразделяется на подкатегории «пешеходы» и«транспортные средства», транспортные средства, в свою очередь, делятся на «велосипеды», «гужевые повозки» и «механические транспортныесредства», эти последние делятся на «трамваи» и «автомобили», автомобили — на грузовые и легковые, и т.
д.Легко заметить, что в такой иерархии каждая категория объектов(в том числе и такая, которая сама подразделяется на категории) обладает некоторыми свойствами, специфичными для данной категории,причём этими же свойствами обладают и все объекты из подкатегорийданной категории. Так, все автомобили имеют, по-видимому, характеристики «объём двигателя», «тип топлива», «объём топливного бака» и «количество топлива в баке», у них может заканчиваться топливо, над нимидолжна быть определена операция «заправка топлива». При этом все этихарактеристики не имеют никакого смысла для объектов, не входящихв категорию «автомобили» — ни для трамваев, ни для велосипедов, нитем более для пешеходов. С другой стороны, у грузовых автомобилейесть характеристика «объём кузова» и операции «погрузка» и «разгрузка», которых нет у автомобилей, не являющихся грузовыми.
Наконец,такие характеристики, как текущая скорость и направление движения,85очевидно, присущи каждому объекту рассматриваемой иерархии, вплотьдо пешеходов.Таким образом, у нас возникают структуры данных, имеющие, с одной стороны, различный набор полей и обрабатывающих функций, но, сдругой стороны, имеющие и некоторые общие поля, и определённые общие операции.
Существуют весьма различные подходы к решению возникшего противоречия. При работе на языке Си чаще всего выходят изположения описанием структур данных, у которых первые несколько полей совпадают; это сопровождается частым использованием преобразования указателей. Ясно, что такой подход чреват множеством труднообнаружимых ошибок.
Объектно-ориентированные языки программированияпредлагают более корректный подход, называемый обычно наследованием.§4.2. Н аследован и е стр у к ту р д ан н ы х ип о л и м о р ф и зм адресовС точки зрения структур данных, расположенных в памяти, наследование представляет собой добавление новых полей данных к ранее описанной структуре. С теоретической точки зрения такое добавление представляет собой уточнение сведений об описываемом объекте, и, такимобразом, наследование рассм атри вается как переход от общ его кчастному. Пусть, например, у нас имеется структура данных, описывающая персону (человека) и содержащая поля для имени, пола и годарождения:stru c t person {char name[64];char sex; / / ’m’ or ’ f ’in t y ear_of_birth ;Пусть теперь нам необходима структура данных, описывающая студент а (частный случай персоны).