11. Обработка исключенийОбработка исключений – это механизм, позволяющий двум независимо разработанным программным компонентам взаимодействовать в аномальной ситуации, называемой исключением. В этой главе мы расскажем, как генерировать, или возбуждать, исключение в том месте программы, где имеет место аномалия. Затем мы покажем, как связать catch-обработчик исключений с множеством инструкций программы, используя try-блок. Потом речь пойдет о спецификации исключений – механизме, с помощью которого можно связать список исключений с объявлением функции, и функция не сможет возбудить никаких других исключений. Закончится эта глава обсуждением решений, принимаемых при проектировании программы, в которой используются исключения. 11.1. Возбуждение исключенияИсключение – это аномальное поведение во время выполнения,
которое программа может обнаружить, например: деление на 0, выход
за границы массива или истощение свободной памяти. Такие исключения
нарушают нормальный ход работы программы, и на них нужно немедленно
отреагировать. В C++ имеются встроенные средства для их возбуждения и
обработки. С помощью этих средств активизируется механизм, позволяющий
двум несвязанным (или независимо разработанным) фрагментам программы
обмениваться информацией об исключении. #include iStack выглядит следующим образом: }; Стек реализован на основе вектора из элементов типа int. При создании объекта класса iStack его конструктор создает вектор из int, размер которого (максимальное число элементов, хранящихся в стеке) задается с помощью начального значения. Например, следующая инструкция создает объект myStack, который способен содержать не более 20 элементов типа int: iStack myStack(20); При манипуляциях с объектом myStack могут возникнуть две ошибки:
Вызвавшую функцию нужно уведомить об этих ошибках посредством исключений. С
чего же начать? // stackExcp.h class popOnEmpty { /* ... */ }; class pushOnFull { /* ... */ }; В главе 19 исключения в виде классов обсуждаются более подробно, там же рассматривается иерархия
таких классов, предоставляемая стандартной библиотекой C++. // увы, это не совсем правильно throw popOnEmpty; К сожалению, так нельзя. Исключение – это объект, и функция pop() должна генерировать объект класса соответствующего типа. Выражение в инструкции throw не может быть просто типом. Для создания нужного объекта необходимо вызвать конструктор класса. Инструкция throw для функции pop() будет выглядеть так: // инструкция является вызовом конструктора throw popOnEmpty(); Эта инструкция создает объект исключения типа popOnEmpty. class iStack { public: // ... // больше не возвращают значения void pop( int &value ); void push( int value ); private: // ... }; Теперь функции, пользующиеся нашим классом iStack, будут предполагать, что все хорошо, если только не возбуждено исключение; им больше не надо проверять возвращенное значение, чтобы узнать, как завершилась операция. В двух следующих разделах мы покажем, как определить функцию для обработки исключений, а сейчас представим новые реализации функций-членов pop() и push() класса iStack: #include "stackExcp.h" void iStack::pop( int &top_value ) { if ( empty() ) throw popOnEmpty(); top_value = _stack[ --_top ]; cout << "iStack::pop(): "<<top_value " endl; } void iStack::push( int value ) { cout << "iStack::push( "<< value << " )\n"; if ( full() ) throw pushOnFull( value ); _stack[ _top++ ] = value; } Хотя исключения чаще всего представляют собой объекты типа класса, инструкция throw может генерировать объекты любого типа. Например, функция mathFunc() в следующем примере возбуждает исключение в виде объекта-перечисления . Это корректный код C++: enum EHstate { noErr, zeroOp, negativeOp, severeError }; int mathFunc( int i ) { if ( i == 0 ) throw zeroOp; // исключение в виде объекта-перечисления // в противном случае продолжается нормальная обработка } Упражнение 11.1 (a) class exceptionType { }; throw exceptionType(); (b) int excpObj; throw excpObj; (c) enum mathErr { overflow, underflow, zeroDivide }; throw mathErr zeroDivide(); (d) int *pi = excpObj; throw pi; Упражнение 11.2 11.2. Try-блокВ нашей программе тестируется определенный в предыдущем разделе класс iStack и его функции-члены pop() и push(). Выполняется 50 итераций цикла for. На каждой итерации в стек помещается значение, кратное 3: 3, 6, 9 и т.д. Если значение кратно 4 (4, 8, 12...), то выводится текущее содержимое стека, а если кратно 10 (10, 20, 30...), то с вершины снимается один элемент, после чего содержимое стека выводится снова. Как нужно изменить функцию main(), чтобы она обрабатывала исключения, возбуждаемые функциями-членами класса iStack? #include Инструкции, которые могут возбуждать исключения, должны быть заключены в try-блок. Такой блок начинается с ключевого слова try, за которым идет последовательность инструкций, заключенная в фигурные скобки, а после этого – список обработчиков, называемых catch-предложениями. Try-блок группирует инструкции программы и ассоциирует с ними обработчики исключений. Куда нужно поместить try-блоки в функции main(), чтобы были обработаны исключения popOnEmpty и pushOnFull? for ( int ix = 1; ix < 51; ++ix ) { try { // try-блок для исключений pushOnFull if ( ix % 3 == 0 ) stack.push( ix ); } catch ( pusOnFull ) { ... } if ( ix % 4 == 0 ) stack.display(); try { // try-блок для исключений popOnEmpty if ( ix % 10 == 0 ) { int dummy; stack.pop( dummy ); stack.display(); } } catch ( popOnEmpty ) { ... } } В таком виде программа выполняется корректно. Однако обработка исключений в ней перемежается с кодом, использующимся при нормальных обстоятельствах, а такая организация несовершенна. В конце концов, исключения – это аномальные ситуации, возникающие только в особых случаях. Желательно отделить код для обработки аномалий от кода, реализующего операции со стеком. Мы полагаем, что показанная ниже схема облегчает чтение и сопровождение программы: try { for ( int ix = 1; ix < 51; ++ix ) { if ( ix % 3 == 0 ) stack.push( ix ); if ( ix % 4 == 0 ) stack.display(); if ( ix % 10 == 0 ) { int dummy; stack.pop( dummy ); stack.display(); } } } catch ( pushOnFull ) { ... } catch ( popOnEmpty ) { ... } С try-блоком ассоциированы два catch-предложения, которые
могут обработать исключения pushOnFull и popOnEmpty, возбуждаемые
функциями-членами push() и pop() внутри этого блока. Каждый catch-обработчик
определяет тип "своего" исключения. Код для обработки исключения помещается
внутрь составной инструкции (между фигурными скобками), которая является частью
catch-обработчика. (Подробнее catch-предложения мы рассмотрим в следующем разделе.)
Когда возбуждается исключение, пропускаются все инструкции, следующие за той, где оно было возбуждено. Исполнение программы возобновляется в catch-обработчике этого исключения. Если такого обработчика не существует, то управление передается в функцию terminate(), определенную в стандартной библиотеке C++. Try-блок может содержать любую инструкцию языка C++: как выражения, так и объявления. Он вводит локальную область видимости, так что объявленные внутри него переменные недоступны вне этого блока, в том числе и в catch-обработчиках. Например, функцию main() можно переписать так, что объявление переменной stack окажется в try-блоке. В таком случае обращаться к этой переменной в catch-обработчиках нельзя: int main() { try { iStack stack( 32 ); // правильно: объявление внутри try-блока stack.display(); for ( int ix = 1; ix < 51; ++ix ) { // то же, что и раньше } } catch ( pushOnFull ) { // здесь к переменной stack обращаться нельзя } catch ( popOnEmpty ) { // здесь к переменной stack обращаться нельзя } // и здесь к переменной stack обращаться нельзя return 0; } Можно объявить функцию так, что все ее тело будет заключено в try-блок. При этом не обязательно помещать try-блок внутрь определения функции, удобнее заключить ее тело в функциональный try-блок. Такая организация поддерживает наиболее чистое разделение кода для нормальной обработки и кода для обработки исключений. Например: int main() try { iStack stack( 32 ); // правильно: объявление внутри try-блока stack.display(); for ( int ix = 1; ix < 51; ++ix ) { // то же, что и раньше } return 0; } catch ( pushOnFull ) { // здесь к переменной stack обращаться нельзя } catch ( popOnEmpty ) { // здесь к переменной stack обращаться нельзя } Обратите внимание, что ключевое слово
try находится перед фигурной скобкой, открывающей
тело функции, а catch-обработчики перечислены после
закрывающей его скобки. Как видим, код, осуществляющий
нормальную обработку, находится внутри тела функции и четко
отделен от кода для обработки исключений. Однако к переменным,
объявленным в main(), нельзя обратиться из обработчиков исключений.
Воспользуйтесь оператором operator[]() класса IntArray, определенным в упражнении 11.2, для сохранения и получения значений из объекта IntArray. Так как operator[]() может возбуждать исключения, обработайте их, поместив необходимое количество try-блоков и catch-обработчиков. Объясните, почему вы разместили try-блоки именно так, а не иначе. 11.3. Перехват исключенийВ языке C++ исключения обрабатываются в предложениях
catch. Когда какая-то инструкция внутри try-блока возбуждает
исключение, то просматривается список последующих предложений
catch в поисках такого, который может его обработать. catch ( pushOnFull ) { cerr << "trying to push value on a full stack\n"; return errorCode88; } catch ( popOnEmpty ) { cerr << "trying to pop a value on an empty stack\n"; return errorCode89; } В обоих catch-обработчиках есть
объявление типа класса; в первом это pushOnFull, а
во втором – popOnEmpty. Для обработки исключения выбирается тот
обработчик, для которого типы в объявлении исключения и в возбужденном
исключении совпадают. (В главе 19 мы увидим, что типы не обязаны совпадать точно:
обработчик для базового класса подходит и для исключений с производными классами.)
Например, когда функция-член pop() класса iStack возбуждает исключение popOnEmpty, то
управление попадает во второй обработчик. После вывода сообщения об ошибке в cerr, функция
main() возвращает код errorCode89. int main() { iStack stack( 32 ); try { stack.display(); for ( int x = 1; ix < 51; ++ix ) { // то же, что и раньше } } catch ( pushOnFull ) { cerr << "trying to push value on a full stack\n"; } catch ( popOnEmpty ) { cerr << "trying to pop a value on an empty stack\n"; } // исполнение продолжается отсюда return 0; } Говорят, что механизм обработки исключений в C++ невозвратный: после того как исключение обработано, управление не возобновляется с того места, где оно было возбуждено. В нашем примере управление не возвращается в функцию-член pop(), возбудившую исключение. 11.3.1. Объекты-исключенияОбъявлением исключения в catch-обработчике
могут быть объявления типа или объекта. В каких
случаях это следует делать? Тогда, когда необходимо
получить значение или как-то манипулировать объектом,
созданным в выражении throw. Если классы исключений спроектированы
так, что в объектах-исключениях при возбуждении сохраняется
некоторая информация и если в объявлении исключения фигурирует
такой объект, то инструкции внутри catch-обработчика могут
обращаться к информации, сохраненной в объекте выражением throw. // новый класс исключения: // он сохраняет значение, которое не удалось поместить в стек class pushOnFull { public: pushOnFull( int i ) : _value( i ) { } int value { return _value; } private: int _value; }; Новый закрытый член _value содержит число, которое не удалось поместить в стек. Конструктор принимает значение типа int и сохраняет его в члене _data. Вот как вызывается этот конструктор для сохранения значения из выражения throw: void iStack::push( int value ) { if ( full() ) // значение, сохраняемое в объекте-исключении throw pushOnFull( value ); // ... } У класса pushOnFull появилась также новая функция-член value(), которую можно использовать в catch-обработчике для вывода хранящегося в объекте-исключении значения: catch ( pushOnFull eObj ) { cerr << "trying to push value << "eObj.value() << "on a full stack\n"; } Обратите внимание, что в объявлении
исключения в catch-обработчике фигурирует объект eObj,
с помощью которого вызывается функция-член value() класса pushOnFull. enum EHstate { noErr, zeroOp, negativeOp, severeError }; enum EHstate state = noErr; int mathFunc( int i ) { if ( i == 0 ) { state = zeroOp; throw state; // создан объект-исключение } // иначе продолжается обычная обработка } В этом примере объект state
не используется в качестве объекта-исключения.
Вместо этого выражением throw создается объект-исключение
типа EHstate, который инициализируется значением глобального
объекта state. Как программа может различить их? Для ответа на этот
вопрос мы должны присмотреться к объявлению исключения в catch-обработчике
более внимательно. void calculate( int op ) { try { mathFunc( op ); } catch ( EHstate eObj ) { // eObj - копия сгенерированного объекта-исключения } } Объявление исключения в этом примере напоминает
передачу параметра по значению. Объект eObj инициализируется
значением объекта-исключения точно так же, как переданный по
значению формальный параметр функции – значением соответствующего
фактического аргумента. (Передача параметров по значению рассматривалась
в разделе 7.3) void calculate( int op ) { try { mathFunc( op ); } catch ( EHstate &eObj ) { // eObj ссылается на сгенерированный объект-исключение } } Для предотвращения ненужного копирования
больших объектов применять ссылки следует не только
в объявлениях параметров типа класса, но и в объявлениях
исключений того же типа. void calculate( int op ) { try { mathFunc( op ); } catch ( EHstate &eObj ) { // исправить ошибку, вызвавшую исключение eObj = noErr; // глобальная переменная state не изменилась } } Catch-обработчик переустанавливает eObj в noErr после исправления ошибки, вызвавшей исключение. Поскольку eObj – это ссылка, можно ожидать, что присваивание модифицирует глобальную переменную state. Однако изменяется лишь объект-исключение, созданный в выражении throw, поэтому модификация eObj не затрагивает state. 11.3.2. Раскрутка стекаПоиск catch-обработчикадля возбужденного
исключения происходит следующим образом. Когда
выражение throw находится в try-блоке, все ассоциированные
с ним предложения catch исследуются с точки зрения того, могут
ли они обработать исключение. Если подходящее предложение catch
найдено, то исключение обрабатывается. В противном случае поиск
продолжается в вызывающей функции. Предположим, что вызов функции,
выполнение которой прекратилось в результате исключения, погружен в
try-блок; в такой ситуации исследуются все предложения catch, ассоциированные
с этим блоком. Если один из них может обработать исключение, то процесс
заканчивается. В противном случае переходим к следующей по порядку вызывающей
функции. Этот поиск последовательно проводится во всей цепочке вложенных
вызовов. Как только будет найдено подходящее предложение, управление передается
в соответствующий обработчик. 11.3.3. Повторное возбуждение исключенияМожет оказаться так, что в одном предложении catch не удалось полностью обработать исключение. Выполнив некоторые корректирующие действия, catch-обработчик может решить, что дальнейшую обработку следует поручить функции, расположенной "выше" в цепочке вызовов. Передать исключение другому catch-обработчику можно с помощью повторного возбуждения исключения. Для этой цели в языке предусмотрена конструкция throw; которая вновь генерирует объект-исключение. Повторное возбуждение возможно только внутри составной инструкции, являющейся частью catch-обработчика: catch ( exception eObj ) { if ( canHandle( eObj ) ) // обработать исключение return; else // повторно возбудить исключение, чтобы его перехватил другой // catch-обработчик throw; } При повторном возбуждении новый объект-исключение не создается. Это имеет значение, если catch-обработчик модифицирует объект, прежде чем возбудить исключение повторно. В следующем фрагменте исходный объект-исключение не изменяется. Почему? enum EHstate { noErr, zeroOp, negativeOp, severeError }; void calculate( int op ) { try { // исключение, возбужденное mathFunc(), имеет значение zeroOp mathFunc( op ); } catch ( EHstate eObj ) { // что-то исправить // пытаемся модифицировать объект-исключение eObj = severeErr; // предполагалось, что повторно возбужденное исключение будет // иметь значение severeErr throw; } } Так как eObj не является ссылкой, то catch-обработчик
получает копию объекта-исключения, так что любые модификации
eObj относятся к локальной копии и не отражаются на исходном
объекте-исключении, передаваемом при повторном возбуждении.
Таким образом, переданный далее объект по-прежнему имеет тип
zeroOp. catch ( EHstate &eObj ) { // модифицируем объект-исключение eObj = severeErr; // повторно возбужденное исключение имеет значение severeErr throw; }
Теперь eObj ссылается на объект-исключение, созданный выражением throw,
так что все изменения относятся непосредственно к исходному объекту.
Поэтому при повторном возбуждении исключения далее передается модифицированный объект. 11.3.4. Перехват всех исключенийИногда функции нужно выполнить определенное действие до того, как она завершит обработку исключения, даже несмотря на то, что обработать его она не может. К примеру, функция захватила некоторый ресурс, скажем открыла файл или выделила память из хипа, и этот ресурс необходимо освободить перед выходом: void manip() { resource res; res.lock(); // захват ресурса // использование ресурса // действие, в результате которого возбуждено исключение res.release(); // не выполняется, если возбуждено исключение } Если исключение возбуждено, то управление не
попадет на инструкцию, где ресурс освобождается. Чтобы
освободить ресурс, не пытаясь перехватить все возможные
исключения (тем более, что мы не всегда знаем, какие именно
исключения могут возникнуть), воспользуемся специальной конструкцией,
позволяющей перехватывать любые исключения. Это не что иное, как
предложение catch, в котором объявление исключения имеет вид (...)
и куда управление попадает при любом исключении. // управление попадает сюда при любом возбужденном исключении catch (...) { // здесь размещаем наш код } Конструкция catch(...) используется в сочетании с повторным возбуждением исключения. Захваченный ресурс освобождается внутри составной инструкции в catch-обработчике перед тем, как передать исключение по цепочке вложенных вызовов в результате повторного возбуждения: void manip() { resource res; res.lock(); try { // использование ресурса // действие, в результате которого возбуждено исключение } catch (...) { res.release(); throw; } res.release(); // не выполняется, если возбуждено исключение } Чтобы гарантировать освобождение ресурса в
случае, когда выход из manip() происходит в результате
исключения, мы освобождаем его внутри catch(...) до того,
как исключение будет передано дальше. Можно также управлять
захватом и освобождением ресурса путем инкапсуляции в класс
всей работы с ним. Тогда захват будет реализован в конструкторе,
а освобождение – в автоматически вызываемом деструкторе. (С этим
подходом мы познакомимся в главе 19.) try { stack.display(); for ( int ix = 1; ix < 51; ++x ) { // то же, что и выше } } catch ( pushOnFull ) { } catch ( popOnEmpty ) { } catch ( ... ) { } // должно быть последним в списке catch-обработчиков Упражнение 11.4 (a) class exceptionType { }; catch( exceptionType *pet ) { } (b) catch(...) { } (c) enum mathErr { overflow, underflow, zeroDivide }; catch( mathErr &ref ) { } (d) typedef int EXCPTYPE; catch( EXCPTYPE ) { }Упражнение 11.6 Объясните, что происходит во время раскрутки стека. Упражнение 11.7 Назовите две причины, по которым объявление исключения в предложении catch следует делать ссылкой. Упражнение 11.8 На основе кода, написанного вами в упражнении 11.3, модифицируйте класс созданного исключения: неправильный индекс, использованный в операторе operator[](), должен сохраняться в объекте-исключении и затем выводиться catch-обработчиком. Измените программу так, чтобы operator[]() возбуждал при ее выполнении исключение. 11.4. Спецификации исключенийПо объявлениям функций-членов pop() и push() класса iStack невозможно определить, что они возбуждают исключения. Можно, конечно, включить в объявление подходящий комментарий. Тогда описание интерфейса класса в заголовочном файле будет содержать документацию возбуждаемых исключений: class iStack { public: // ... void pop( int &value ); // возбуждает popOnEmpty void push( int value ); // возбуждает pushOnFull private: // ... }; Но такое решение несовершенно. Неизвестно,
будет ли обновлена документация при выпуске следующих
версий iStack. Кроме того, комментарий не дает компилятору
достоверной информации о том, что никаких других исключений
функция не возбуждает. Спецификация исключений позволяет перечислить
в объявлении функции все исключения, которые она может возбуждать.
При этом гарантируется, что другие исключения функция возбуждать не будет. class iStack { public: // ... void pop( int &value ) throw(popOnEmpty); void push( int value ) throw(pushOnFull); private: // ... }; Гарантируется, что при обращении к pop()
не будет возбуждено никаких исключений, кроме
popOnEmpty, а при обращении к push()–только pushOnFull. // два объявления одной и той же функции extern int foo( int = 0 ) throw(string); // ошибка: опущена спецификация исключений extern int foo( int parm ) { } Что произойдет, если функция возбудит
исключение, не перечисленное в ее спецификации?
Исключения возбуждаются только при обнаружении
определенных аномалий в поведении программы, и во время
компиляции неизвестно, встретится ли то или иное исключение
во время выполнения. Поэтому нарушения спецификации исключений
функции могут быть обнаружены только во время выполнения. Если
функция возбуждает исключение, не указанное в спецификации, то
вызывается unexpected() из стандартной библиотеки C++, а та по
умолчанию вызывает terminate(). (В некоторых случаях необходимо
переопределить действия, выполняемые функцией unexpected(). Стандартная
библиотека предоставляет механизм для этого. Подробнее см. [STRAUSTRUP97].) void recoup( int op1, int op2 ) throw(ExceptionType) { try { // ... throw string("we're in control"); } // обрабатывается возбужденное исключение catch ( string ) { // сделать все необходимое } } // все хорошо, unexpected() не вызывается Функция recoup() возбуждает исключение типа
string, несмотря на его отсутствие в спецификации.
Поскольку это исключение обработано в теле функции,
unexpected() не вызывается. extern void doit( int, int ) throw(string, exceptionType); void action ( int op1, int op2 ) throw(string) { doit( op1, op2 ); // ошибки компиляции не будет // ... } doit() может возбудить исключение типа exceptionType,
которое не разрешено спецификацией action(). Однако функция
компилируется успешно. Компилятор при этом генерирует код,
гарантирующий, что при возбуждении исключения, нарушающего
спецификацию, будет вызвана библиотечная функция unexpected(). extern void no_problem () throw(); Если же в объявлении функции спецификация
исключений отсутствует, то может быть возбуждено
исключение любого типа. int convert( int parm ) throw(string) { //... if ( somethingRather ) // ошибка программы: // convert() не допускает исключения типа const char* throw "help!"; } Выражение throw в функции convert() возбуждает исключение типа строки символов в стиле языка C. Созданный объект-исключение имеет тип const char*. Обычно выражение типа const char* можно привести к типу string. Однако спецификация не допускает преобразования типов, поэтому если convert() возбуждает такое исключение, то вызывается unexpected(). Для исправления ошибки выражение throw можно модифицировать так, чтобы оно явно преобразовывало значение выражения в тип string: throw string( "help!" ); 11.4.1. Спецификации исключений и указатели на функцииСпецификацию исключений можно задавать и при объявлении указателя на функцию. void (*pf)( int ) throw(string); В этом объявлении говорится, что pf указывает на функцию, которая способна возбуждать только исключения типа string. Как и для объявлений функций, спецификации исключений в разных объявлениях одного и того же указателя не суммируются, они должны быть одинаковыми: extern void (*pf) ( int ) throw(string); // ошибка: отсутствует спецификация исключения void (*pf)( int ); При работе с указателем на функцию со спецификацией исключений есть ограничения на тип указателя, используемого в качестве инициализатора или стоящего в правой части присваивания. Спецификации исключений обоих указателей не обязаны быть идентичными. Однако на указатель-инициализатор она должна накладывать столь же или более строгие ограничения, чем на инициализируемый указатель (или тот, которому присваивается значение). Например: void recoup( int, int ) throw(exceptionType); void no_problem() throw(); void doit( int, int ) throw(string, exceptionType); // правильно: ограничения, накладываемые на спецификации // исключений recoup() и pf1, одинаковы void (*pf1)( int, int ) throw(exceptionType) = &recoup; // правильно: ограничения, накладываемые на спецификацию исключений no_problem(), более строгие, // чем для pf2 void (*pf2)( ) throw(string) = &no_problem; // ошибка: ограничения, накладываемые на спецификацию // исключений doit(), менее строгие, чем для pf3 // void (*pf3)( int, int ) throw(string) = &doit; Третья инициализация не имеет смысла. Объявление
указателя гарантирует, что pf3 адресует функцию, которая
может возбуждать только исключения типа string. Но doit()
возбуждает также исключения типа exceptionType. Поскольку
она не подходит под ограничения, накладываемые спецификацией
исключений pf3, то не может служить корректным инициализатором
для pf3, так что компилятор выдает ошибку. void example() throw(string); (a) void (*pf1)() = example; (b) void (*pf2) throw() = example; 11.5. Исключения и вопросы проектированияС обработкой исключений в программах C++ связано несколько
вопросов. Хотя поддержка такой обработки встроена в язык, не стоит
использовать ее везде. Обычно она применяется для обмена информацией
об ошибках между независимо разработанными частями программы.
Например, автор некоторой библиотеки может с помощью исключений
сообщать пользователям об ошибках. Если библиотечная функция
обнаруживает аномальную ситуацию, которую не способна обработать
самостоятельно, она может возбудить исключение для уведомления
вызывающей программы. void iStack::push( int value ) { // если стек полон, увеличить размер вектора if ( full() ) _stack.resize( 2 * _stack.size() ); _stack[ _top++ ] = value; } Аналогично следует ли функции pop() возбуждать исключение
при попытке извлечь значение из пустого стека? Интересно отметить,
что класс stack из стандартной библиотеки C++ (он рассматривался в
главе 6) не возбуждает исключения в такой ситуации. Вместо этого
постулируется, что поведение программы при попытке выполнения
подобной операции не определено. Разрешить программе продолжать
работу при обнаружении некорректного состояния признали возможным.
Мы уже упоминали, что в разных библиотеках определены разные исключения.
Не существует пригодного для всех случаев ответа на вопрос, что такое
исключение. Содержание |
2011-12-14 22:54:19 Juri Хорошая статья 2012-04-12 15:49:07 Вячеслав Большое спасибо! 2016-06-26 14:42:54 Алексей Спасибо, очень содержательно! 2017-01-04 12:42:06 Lexanni Большое спасибо, очень помогли! :–) Оставить комментарий: |