straustrup2 (852740), страница 64
Текст из файла (страница 64)
возникнет бесконечныйцикл. Поэтому управляющая функция может запустить особую ситуацию и предоставить исправлятьположение программе, обратившейся к new:void my_new_handler(){try_find_some_memory();if (found_some()) return;throw Memory_exhausted();//////////попытаемся найтисвободную памятьесли она найдена, все в порядкеиначе запускаем особуюситуацию "Исчерпание_памяти"}Где-то в программе должен быть проверяемый блок с соответствующим обработчиком:try {// ...}catch (Memory_exhausted) {// ...}В функции operator new() использовался указатель на управляющую функцию _new_handler, которыйнастраивается стандартной функцией set_new_handler().
Если нужно настроиться на собственнуюуправляющую функцию, надо обратиться такset_new_handler(&my_new_handler);Перехватить ситуацию Memory_exhausted можно следующим образом:void (*oldnh)() = set_new_handler(&my_new_handler);try {// ...}catch (Memory_exhausted) {// ...}catch (...) {set_new_handler(oldnh);// восстановить указатель на// управляющую функциюthrow();// повторный запуск особой ситуации}set_new_handler(oldnh);// восстановить указатель на// управляющую функциюМожно поступить еще лучше, если к управляющей функции применить описанный в $$9.4 метод"запроса ресурсов путем инициализации" и убрать обработчик catch (...).В решении, использующим my_new_handler(), от точки обнаружения ошибки до функции, в которой онаобрабатывается, не передается никакой информации.
Если нужно передать какие-то данные, топользователь может включить свою управляющую функцию в класс. Тогда в функции, обнаружившейошибку, нужные данные можно поместить в объект этого класса. Подобный способ, использующийобъекты-функции, применялся в $$10.4.2 для реализации манипуляторов. Способ, в которомиспользуется указатель на функцию или объект-функция для того, чтобы из управляющей функции,обслуживающей некоторый ресурс, произвести "обратный вызов" функции запросившей этот ресурс,обычно называется просто обратным вызовом (callback).При этом нужно понимать, что чем больше информации передается из обнаружившей ошибку функции247Бьерн Страуструп.Язык программирования С++в функцию, пытающуюся ее исправить, тем больше зависимость между этими двумя функциями.
Вобщем случае лучше сводить к минимуму такие зависимости, поскольку всякое изменение в одной изфункций придется делать с учетом другой функцией, а, возможно, ее тоже придется изменять. Вообще,лучше не смешивать отдельные компоненты программы. Механизм особых ситуаций позволяетсохранять раздельность компонентов лучше, чем обычный механизм вызова управляющих функций,которые задает функция, затребовавшая ресурс.В общем случае разумный подход состоит в том, чтобы выделение ресурсов было многоуровневым (всоответствии с уровнями абстракции). При этом нужно избегать того, чтобы функции одного уровнязависели от управляющей функции, вызываемой на другом уровне.
Опыт создания большихпрограммных систем показывает, что со временем удачные системы развиваются именно в этомнаправлении.9.4.4 Особые ситуации и конструкторыОсобые ситуации дают средство сигнализировать о происходящих в конструкторе ошибках. Посколькуконструктор не возвращает такое значение, которое могла бы проверить вызывающая функция, естьследующие обычные (т.е. не использующие особые ситуации) способы сигнализации:[1]Возвратить объект в ненормальном состоянии в расчете, что пользователь проверит егосостояние.[2]Установить значение нелокальной переменной, которое сигнализирует, что создать объект неудалось.Особые ситуации позволяют тот факт, что создать объект не удалось, передать из конструктора вовне:Vector::Vector(int size){if (sz<0 || max<sz) throw Size();// ...}В функции, создающей вектора, можно перехватить ошибки, вызванные недопустимым размером(Size()) и попытаться на них отреагировать:Vector* f(int i){Vector* p;try {p = new Vector v(i);}catch (Vector::Size) {// реакция на недопустимый размер вектора}// ...return p;}Управляющая созданием вектора функция способна правильно отреагировать на ошибку.
В самомобработчике особой ситуации можно применить какой-нибудь из стандартных способов диагностики ивосстановления после ошибки. При каждом перехвате особой ситуации в управляющей функции можетбыть свой взгляд на причину ошибки. Если с каждой особой ситуацией передаются описывающие ееданные, то объем данных, которые нужно анализировать для каждой ошибки, растет.
Основная задачаобработки ошибок в том, чтобы обеспечить надежный и удобный способ передачи данных от исходнойточки обнаружения ошибки до того места, где после нее возможно осмысленное восстановление.Способ "запроса ресурсов путем инициализации" - самый надежное и красивое решение в том случае,когда имеются конструкторы, требующие более одного ресурса.
По сути он позволяет свести задачувыделения нескольких ресурсов к повторно применяемому, более простому, способу, рассчитанному наодин ресурс.248Бьерн Страуструп.Язык программирования С++9.5 Особые ситуации могут не быть ошибкамиЕсли особая ситуация ожидалась, была перехвачена и не оказала плохого воздействия на ходпрограммы, то стоит ли ее называть ошибкой? Так говорят только потому, что программист думает оней как об ошибке, а механизм особых ситуаций является средством обработки ошибок. С другойстороны, особые ситуации можно рассматривать просто как еще одну структуру управления.Подтвердим это примером:class message { /* ...
*/ };class queue {// ...message* get();// ...};// сообщение// очередь// вернуть 0, если очередь пустаvoid f1(queue& q){message* m = q.get();if (m == 0) { // очередь пуста// ...}// используем m}Этот пример можно записать так:class Empty { } // тип особой ситуации "Пустая_очередь"class queue {// ...message* get(); // запустить Empty, если очередь пуста// ...};void f2(queue& q){try {message* m = q.get();// используем m}catch (Empty) { // очередь пуста// ...}}В варианте с особой ситуацией есть даже какая-то прелесть. Это хороший пример того, когда трудносказать, можно ли считать такую ситуацию ошибкой.
Если очередь не должна быть пустой (т.е. онабывает пустой очень редко, скажем один раз из тысячи), и действия в случае пустой очереди можнорассматривать как восстановление, то в функции f2() взгляд на особую ситуацию будет такой, которогомы до сих пор и придерживались (т.е. обработка особых ситуаций есть обработка ошибок). Еслиочередь часто бывает пустой, а принимаемые в этом случае действия образуют одну из ветвейнормального хода программы, то придется отказаться от такого взгляда на особую ситуацию, афункцию f2() надо переписать:class queue {// ...message* get();int empty();// ...};// запустить Empty, если очередь пуста249Бьерн Страуструп.Язык программирования С++void f3(queue& q){if (q.empty()) { // очередь пуста// ...}else {message* m = q.get();// используем m}}Отметим, что вынести из функции get() проверку очереди на пустоту можно только при условии, что кочереди нет параллельных обращений.Не так то просто отказаться от взгляда, что обработка особой ситуации есть обработка ошибки.
Покамы придерживаемся такой точки зрения, программа четко подразделяется на две части: обычная частьи часть обработки ошибок. Такая программа более понятна. К сожалению, в реальных задачах провестичеткое разделение невозможно, поэтому структура программы должна (и будет) отражать этот факт.Допустим, очередь бывает пустой только один раз (так может быть, если функция get() используется вцикле, и пустота очереди говорит о конце цикла). Тогда пустота очереди не является чем-то страннымили ошибочным. Поэтому, используя для обозначения конца очереди особую ситуацию, мы расширяемпредставление об особых ситуациях как ошибках. С другой стороны, действия, принимаемые в случаепустой очереди, явно отличаются от действий, принимаемых в ходе цикла (т.е. в обычном случае).Механизм особых ситуаций является менее структурированным, чем такие локальные структурыуправления как операторы if или for.
Обычно он к тому же является не столь эффективным, если особаяситуация действительно возникла. Поэтому особые ситуации следует использовать только в томслучае, когда нет хорошего решения с более традиционными управляющими структурами, или оно,вообще, невозможно. Например, в случае пустой очереди можно прекрасно использовать длясигнализации об этом значение, а именно нулевое значение указателя на строку message, значитособая ситуация здесь не нужна. Однако, если бы из класса queue мы получали вместо указателязначение типа int, то то могло не найтись такого значения, обозначающего пустую очередь.
В такомслучае функция get() становится эквивалентной операции индексации из $$9.1, и более привлекательнопредставлять пустую очередь с помощью особой ситуации. Последнее соображение подсказывает, чтов самом общем шаблоне типа для очереди придется для обозначения пустой очереди использоватьособую ситуацию, а работающая с очередью функция будет такой:void f(Queue<X>& q){try {for (;;) {// ``бесконечный цикл''// прерываемый особой ситуациейX m = q.get();// ...}}catch (Queue<X>::Empty) {return;}}Если приведенный цикл выполняется тысячи раз, то он, по всей видимости, будет более эффективным,чем обычный цикл с проверкой условия пустоты очереди.
Если же он выполняется только несколькораз, то обычный цикл почти наверняка эффективней.В очереди общего вида особая ситуация используется как способ возврата из функции get().Использование особых ситуаций как способа возврата может быть элегантным способом завершенияфункций поиска. Особенно это подходит для рекурсивных функций поиска в дереве. Однако, применяяособые ситуации для таких целей, легко перейти грань разумного и получить маловразумительнуюпрограмму. Все-таки всюду, где это действительно оправдано, надо придерживаться той точки зрения,что обработка особой ситуации есть обработка ошибки. Обработка ошибок по самой своей природе250Бьерн Страуструп.Язык программирования С++занятие сложное, поэтому ценность имеют любые методы, которые дают ясное представление ошибокв языке и способ их обработки.9.6 Задание интерфейсаЗапуск или перехват особой ситуации отражается на взаимоотношениях функций.
Поэтому имеетсмысл задавать в описании функции множество особых ситуаций, которые она может запустить:void f(int a) throw (x2, x3, x4);В этом описании указано, что f() может запустить особые ситуации x2, x3 и x4, а также ситуации всехпроизводных от них типов, но больше никакие ситуации она не запускает.