|
Программирование >> Полиморфизм без виртуальных функций в с++
Однако это выпадало из стиля, принятого в других частях С++, и многие, в том числе Тед Голдстейн и Питер Дойч (Peter Deutsch), отмечали, что такие группы по сути эквивалентны иерархии классов. Поэтому мы остановились на схеме, навеянной языком ML: возбужденное исключение-объект перехватывается обработчиком, в объявлении которого говорится, что он может принимать объекты такого типа. При этом обычные правила инициализации С++ позволяют обработчику объектов типа В перехватывать объекты любого класса D, производного от В. Например: class Matherr { }; class Overflow : public Matherr { }; class Underflow : public Matherr { }; class Zerodivide : public Matherr { }; ... void g() { try { f 0; catch (Overflow) { обработать исключение типа Overflow или любого производного от него catch (Matherr) { обработать любое исключение типа Matherr, кроме Overflow Позже обнаружилось, что множественное наследование (см. главу 12) дает очень красивое решение трудных задач классификации. Например, можно было объявить ошибку при работе с файлом на сетевом диске так: class networlt file err : public networl< err , public f ile system err { } ; Исключение типа network f ile err может обработать как обработчик сетевых ошибок, так и ошибок в файловой системе. Первым на это мне указал Дэ-ниэль Уэйнреб (Daniel Weinreb). 16.5. Управление ресурсами Главным в проекте обработки исключений был вопрос об управлении ресурсами. Скажем, если функция захватывает некоторый ресурс, может ли язык выполнить часть работы по освобождению ресурса при выходе, даже если произошло исключение? Рассмотрим пример, взятый из [2nd]: void use file(const char* fn) { FILE* f = fopen(fn, w ); открыть файл с именем fn fclose(f); закрыть файл fn Выглядит разумно. Но если между вызовами fopen () и fclose () произойдет ошибка, из-за возникшего исключения выход из функции use f ile может быть выполнен без вызова fclose (). То же самое может случиться и в языках, не под-держиваюших исключения. Например, к таким же печальным последствиям приведет вызов функции longjmp () из стандартной библиотеки С. Если мы хотим разрабатывать отказоустойчивые системы, эту проблему придется сни.мать. Примитивное решение выглядит так: void use file(const char* fn) { FILE* f = fopen(fn, r ); открыть файл с именем fn try { использовать f catch (...) { перехватить все исключения fclose(f); закрыть файл fn throw; возбудить повторно fclose(f); закрыть файл fn Весь код для работы с файлом заключен в блок try, который перехватывает любое исключение, закрывает файл и возбуждает то же исключение повторно. К сожалению, данное решение многословно и потенциально и.меет большие затраты. К тому же необходимость писать длинные повторяюшиеся фрагменты уто.мляет программиста и он может наделать ошибок. Можно было слегка сократить код, предоставив в распоряжение программиста некоторый механизм финальной обработки. Это позволило бы уйти от дублирования кода освобождения ресурса (в данном случае fclose (f)), но никак не решило бы принципиальную проблему: для написания отказоустойчивого кода требуется специальная и более сложная, чем обычно, техника. Впрочем, есть и более изящное решение. В общем виде задача выглядит так: void use О { захватить ресурс 1 .. . захватить ресурс п использовать ресурсы освободить ресурс п . . . освободить ресурс 1 Как правило, важно, чтобы ресурсы освобождались в порядке, обратном их захвату. Это весьма напоминает поведение локальных объектов, которые создаются конструкторами и уничтожаются деструктора.ми. Стало быть, мы можем решить проблему захвата и освобождения ресурсов путе.м разумного использования объектов классов, имеющих конструкторы и деструкторы. Например, удалось бы определить класс FilePtr, ведуший себя как FILE*: class FilePtr { FILE* p; public: FilePtr(const char* n, const char* a) { p = fopen(n,a); } FilePtr(FILE* pp) { p = pp; } -FilePtr0 { fclose(p); } operator FILE*() { return p; } Мы можем сконструировать FilePtr, зная либо FILE*, либо аргументы, которые обычно передаются f open (). В любом случае объект FilePtr будет разрушен при выходе из области действия, а его деструктор закроет файл. Теперь наша программа сокращается до минимального размера: void use f Не (const char* fn) { FilePtr f(fn, r ); открыть файл с именем fn использовать f } файл fn закрывается неявно Деструктор вызывается независимо от того, как произошел выход из функции: обычно или в результате исключения. Данный нрие.м я называю захват ресурса - это инициализация . Он может работать с частично сконструированными объектами и таким образом решает довольно трудную проблему: что делать, если ошибка произошла в конструкторе (см. [Koenig, 1990] или [2nd]). 16.5.1. Ошибки в конструкторах Для некоторых пользователей важнейшим аспектом исключений является то, что они дают общий механизм извещения об ошибках, произошедших в конструкторе. Рассмотрим конструктор класса FilePtr: в нем не проверяется, открыт ли файл корректно или нет. Вот наиболее аккуратная запись: FilePtr:-.FilePtr(const char* n, const char* a) { if ((p = fopen(n,a)) == 0) { файл не открылся - что делать? Без исключений нет простого способа сообщить об ошибке: конструктор не возврашает значения. Поэтому приходилось изобретать обходные пути, например переводить наполовину сконструированные объекты в ошибочное состояние.
|
© 2006 - 2025 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |