Скачать презентацию
Идет загрузка презентации. Пожалуйста, подождите
Презентация была опубликована 9 лет назад пользователемБогдан Михальский
1 Лекция 27. Введение в STL (часть 4) Красс Александр СПбГУ ИТМО, 2009
2 2 Исключения Исключение – это аномальное поведение во время выполнения, которое программа может обнаружить, например: деление на 0, выход за границы массива или истощение свободной памяти. Такие исключения нарушают нормальный ход работы программы, и на них нужно немедленно отреагировать.
3 3 Старые способы обработки исключений Завершение работы –Библиотеки не должны сами решать, как им обрабатывать ошибки. Это решение должен принимать вызывающий код Коды возврата –Библиотека обнаружила ошибку, но не факт, что вызывающий код проверил код возврата и отреагировал на ошибку Глобальные переменные –Тоже самое, что и в предыдущем пункте Функции обратного вызова –Ошибка будет обрабатываться где-то в другом месте, не знающим о контексте, в котором эта ошибка возникла. Глобальный переход (setjmp/longjmp) –Не совместим с C++, т.к. при переходе не вызываются деструкторы локальных объектов
4 4 Глобальный переход Реализуется двумя функциями: –setjmp – сохраняет текущее состояние программы –longjmp – восстанавливает это состояние. В итоге получаем глобальный аналог goto, позволяющий прыгать между функциями. Пример: #include "stdafx.h" #include void main() { jmp_buf mark; int jmpRet; jmpRet = setjmp(mark); if (jmpRet == 0) { // Продолжаем работу int a, b; scanf("%d %d", &a, &b); if (b == 0) longjmp(mark, -1); printf("%d", a / b); } else { // Ошибка puts("Bad number"); }
5 5 Постановка задачи Обработка исключений должна быть частью языка Она должна позволять использовать ООП, особенно наследование и полиморфизм Она должна быть гибкой и поддерживать различные стратегии обработки исключений После решения этих задач на свет появились исключения C++
6 6 Введение в исключения При обнаружении ошибки мы должны уведомить вызывающий код о ней. Для этого мы используем оператор throw, генерирующий исключение. Если вызывающий код считает, что в вызываемом коде может возникнуть ошибка, он должен использовать конструкцию try/catch для перехвата и обработки исключений. void WriteData(char *data, int length) { if (!fwrite(…)) throw Cant write to file; // генерируем исключение } try { // В блок try помещается код, который мы хотим проверить на исключение WriteData(…); } catch (char *msg) { // в блок catch помещается обработчик ошибок. std::cout << Error occurred: << msg; }
7 7 Объекты как параметры throw Функция fwrite сообщает о причинах ошибки через глобальную переменную errno В блоке catch эта информация может быть утеряна. Вместо вызова throw … лучше сделаем так: class WriteDataFailed { public: WriteDataFailed(char *s, int rc) : msg(s), retcode(rc) {} char *msg; int retcode; }; void WriteData(char *data, int length) { if (!fwrite(…)) throw WriteDataFailed(Cant write to file, errno); // генерируем исключение } try { WriteData(…); } catch (WriteDataFailed errInfo) { std::cout << Error occurred: << errInfo.msg; } Теперь при возникновении ошибки мы передаем обработчику всю необходимую информацию
8
8 Обработка нескольких ошибок Подход 1 (через if/switch) class WriteDataFailed {…}; void WriteData(char *data, int length) { … } try { WriteData(…); } catch (WriteDataFailed errInfo) { If (errInfo.retcode == ENOSPC) std<
9 9 Использование нескольких блоков catch class WriteDataFailed { public: WriteDataFailed(char *s, int rc) : msg(s), retcode(rc) {} char *msg; int retcode; }; class DiskFull {}; void WriteData(char *data, int length) { if (!fwrite(…)) if (errno == ENOSPC) throw DiskFull(); else throw WriteDataFailed(Cant write to file, errno); // генерируем исключение } try { WriteData(…); } catch (DiskFull ) { std::cout << Disk full; } catch (WriteDataFailed errInfo) { std::cout << Error occurred: << errInfo.msg; }
10 10 Поиск правильного обработчика Оператор throw обладает определенным интеллектом. Он сам определяет, куда ему надо прийти для обработки исключения. Программист только указывает тип исключения. При поиске нужного обработчика учитывается только тип, но не значение. Процесс поиска нужного обработчика называется раскруткой стека
11 11 Раскрутка стека При поиске нужного обработчика оператор throw идет вверх по стеку, вызывая деструкторы локальных объектов и пытается найти среди всех блоков catch нужный. Пример: class e {}; class A { public: A() { std::cout << "A::A"; } ~A() { std::cout << "A::~A"; } }; void f() { try { g();} catch (e) { std::cout << "f: Exception occured"; } } void main() { f(); } void g() { try { throw e(); } catch (int ) { std::cout << "g1: Exception occured"; } catch (float ) { std::cout << g2: Exception occured"; } }
12 12 Необработанные исключения Если в результате раскрутки стека throw не смог найти правильного обработчика, то исключение считается необработанным. При возникновении необработанного исключения throw вызывает функцию terminate, которая в свою очередь вызывает функцию abort, завершающую программу. Вы можете установить свой обработчик необработанных исключений с помощью функции set_terminate. Пример: #include void termfunction( ) { std::cout << "I'll be back." << std::endl; abort( ); } int main( ) { terminate_handler oldHand = set_terminate(termfunction); throw 1; return 0; // строчка не выполнится. Но если не укажем return, компилятор будет ругаться. }
13 13 Обработка всех исключений Иногда хочется в программе обрабатывать все исключения. Нам может быть не интересен тип, а важен сам факт возникновения исключения. Для этого используем блок catch (…), который перехватывает все исключения. Пример: #include void termfunction( ) { std::cout << "I'll be back." << std::endl; abort( ); } int main( ) { terminate_handler oldHand = set_terminate(termfunction); try { throw 1; } catch (…) { // Теперь управление придет сюда std::cout << Unknown exception occurred; } return 0; // А в конце выполнится эта строчка }
14 14 Обработка исключений по ссылке или по адресу Ранее мы использовали обработку исключений по значению: class WriteDataFailed {}; void WriteData(char *data, int length) { } try { WriteData(…); } catch (WriteDataFailed errInfo) { std::cout << Error occurred: << errInfo.msg; } В блок catch экземпляр класса WriteDataFailed приходит по значению. В итоге имеем лишние вызовы конструкторов копирования. Лучше передавать по ссылке или по адресу
15 15 class WriteDataFailed {}; void WriteData(char *data, int length) { if (!fwrite(…)) throw WriteDataFailed(Cant write to file, errno); // генерируем исключение } try { WriteData(…); } catch (WriteDataFailed &errInfo) // по ссылке { std::cout << Error occurred: << errInfo.msg; } А еще лучше по константной ссылке Передача по ссылке позволит использовать наследование и виртуальные функции
16 16 Иерархии исключений Объединение исключений в иерархии позволяет упросить код их обработки. class WriteDiskError { public: WriteDiskError(char *s = Write disk error) : msg(s) {} virtual bool Repair() { return false; } const char *Message() { return msg; } private: char *msg; }; class DiskWriteProtectedError : public WriteDiskError { public: DiskWriteProtectedError() : WriteDiskError(Disk write protected error) {} bool Repair () {…} }; void DoWrite() { try { Write(…); } catch(DiskWriteError &e) { if (e.Repair()) std::cout << e.Message(); }
17 17 Идиома RAII (Resource Acquisition Is Initialization) Все ресурсы желательно оборачивать в классы. Тогда при раскрутке стека будет вызван деструктор и ресурс освободится автоматически. class SmartFILE { public: SmartFILE(const char *name, const char *mode) { fp = fopen(name, mode); } ~SmartFILE() { fclose(fp); } operator FILE*() { return fp; } private: SmartFILE(const SmartFILE &); SmartFILE& operator=(const SmartFILE &); FILE *fp; }; void f() { FILE *fp = fopen("c:\\install.log","wt"); fputs("OK", fp); throw 1; // fp не закроется. Если бы использовали SmartFILE, проблем не было. } void f2() { SmartFILE f(("c:\\install.log","wt"); fputs("OK", f); throw 1; // А теперь проблем нет }
18 18 Исключения в конструкторах и деструкторах Деструктор может генерировать исключения но не должен его выпускать наружу! Конструктор может генерировать исключения. Для конструктора, генерация исключения это единственный способ сказать, что объект не конструировался Во время раскрутки стека из конструктора компилятор не вызывает деструктор объекта, но вызывает деструкторы для сконструированных членов класса, а также для базовых классов.
19 19 Обработка нехватки памяти Оператор new возбуждает исключение std::bad_alloc если памяти не хватает. При необходимости можно использовать специальную форму new, не генерирующую исключения: void f() { char *s1 = new char[100]; если памяти не хватит, сгенерируем исключение char *s2 = new(nothrow) char[100]; если памяти не хватит, вернет 0 } Функция _set_new_handler позволяет указать функцию, вызываемую оператором new при нехватке памяти.
20 20 Спецификация интерфейсов исключений Позволяет для каждой функции указать, какие исключения она может генерировать. 1. class A {}; class B {}; void f() throw (A, B) // генерирует только A или B { } 2. void f() throw() – вообще не генерирует исключений 3. void f() – генерирует все исключения Если функция сгенерировала исключение НЕ из списка, компилятор вызовет функцию unexpected, которая по умолчанию вызовет abort. Спецификация не является частью сигнатуры функции Определение виртуальной функции в производном классе с новой спецификацией отменит полиморфное поведение этой функции Сомнительная возможность языка –накладывает очень сильные ограничения на дальнейшие изменения реализации –Применимо для библиотек общего назначения, например STL VC2005 поддерживает только спецификатор, говорящий, что исключений не будет.
21 21 Спасибо за внимание Вопросы?
Еще похожие презентации в нашем архиве:
© 2024 MyShared Inc.
All rights reserved.