Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Здесь заключенное в круглые скобки многоточие обеспечивает совпадение с любым типом данных.
Использование формата catch(...) иллюстрируется в следующей программе. // В этой программе перехватываются исключения всех типов. #include < iostream> using namespace std; Void Xhandler(int test) { try { if(test==0) throw test; // генерирует int-исключение if(test==1) throw 'a'; // генерирует char-исключение if(test==2) throw 123.23; // генерирует double-исключение } catch (...) { // перехват всех исключений cout < < " Перехват! "; } } Int main() { cout < < " НАЧАЛО"; Xhandler (0); Xhandler (1); Xhandler (2); cout < < " КОНЕЦ"; return 0; } Эта программа генерирует такие результаты. НАЧАЛО Перехват! Перехват! Перехват! КОНЕЦ Как видите, все три throw-исключения перехвачены с помощью одной-единственной catch-инетрукции. Зачастую имеет смысл использовать инструкцию catch(...) в качестве последнего " рубежа" catch-последовательности. В этом случае она обеспечивает перехват исключений " всех остальных" типов (т.е. не предусмотренных предыдущими catch-выражениями). Например, рассмотрим еще одну версию предыдущей программы, в которой явным образом обеспечивается перехват исключений целочисленного типа, а перехват всех остальных возможных исключений " взваливается на плечи" инструкции catch(...). /* Использование формата catch (...) в качестве варианта " все остальное". */ #include < iostream> using namespace std; Void Xhandler(int test) { try { if(test==0) throw test; // генерирует int-исключение if(test==1) throw 'a'; // генерирует char-исключение if(test==2) throw 123.23; // генерирует double-исключение } catch(int i) { // перехватывает int-исключение cout < < " Перехват " < < i < < ''; } catch(...) { // перехватывает все остальные исключения cout < < " Перехват-перехват! "; } } Int main() { cout < < " НАЧАЛО"; Xhandler(0); Xhandler(1); Xhandler(2); cout < < " КОНЕЦ"; return 0; } Результаты, сгенерированные при выполнении этой программы, таковы. НАЧАЛО Перехват 0 Перехват-перехват! Перехват-перехват! КОНЕЦ Как подтверждает этот пример, использование формата catch(...) в качестве " последнего оплота" catch-последовательности— это удобный способ перехватить все исключения, которые вам не хочется обрабатывать в явном виде. Кроме того, перехватывая абсолютно все исключения, вы предотвращаете возможность аварийного завершения программы, которое может быть вызвано каким-то непредусмотренным (а значит, необработанным) исключением. Ограничения, налагаемые на тип исключений, генерируемых функциями Существуют средства, которые позволяют ограничить тип исключений, которые может генерировать функция за пределами своего тела. Можно также оградить функцию от генерирования каких бы то ни было исключений вообще. Для формирования этих ограничений необходимо внести в определение функции throw-выражение. Общий формат определения функции с использованием throw-выражения выглядит так. тип имя_функции(список_аргументов) throw(список_имен_типов) { //... } Здесь элемент список_имен_типов должен включать только те имена типов данных, которые разрешается генерировать функции (элементы списка разделяются запятыми). Генерирование исключения любого другого типа приведет к аварийному окончанию программы. Если нужно, чтобы функция вообще не могла генерировать исключения, используйте в качестве этого элемента пустой список. На заметку. При попытке сгенерировать исключение, которое не поддерживается функцией, вызывается стандартная библиотечная функция unexpected(). По умолчанию она вызывает функцию abort(), которая обеспечивает аварийное завершение программы. Но при желании можно задать собственный обработчик процесса завершения. За подробностями обращайтесь к документации, прилагаемой к вашему компилятору. На примере следующей программы показано, как можно ограничить типы исключений, которые способна генерировать функция. /* Ограничение типов исключений, генерируемых функцией. */ #include < iostream> using namespace std; /* Эта функция может генерировать исключения только типа int, char и double. */ Void Xhandler(int test) throw(int, char, double) { if(test==0) throw test; // генерирует int-исключение if(test==1) throw 'a'; // генерирует char-исключение if(test==2) throw 123.23; // генерирует double-исключение } Int main() { cout < < " НАЧАЛО"; try { Xhandler(0); // Попробуйте также передать функции Xhandler() аргументы 1 и 2. } catch(int i) { cout < < " Перехват int-исключения."; } catch(char c) { cout < < " Перехват char-исключения."; } catch(double d) { cout < < " Перехват double-исключения."; } cout < < " КОНЕЦ"; return 0; } В этой программе функция Xhandler() может генерировать исключения только типа int, char и double. При попытке сгенерировать исключение любого другого типа произойдет аварийное завершение программы (благодаря вызову функции unexpected()). Чтобы убедиться в этом, удалите из throw-списка, например, тип int и перезапустите программу. Важно понимать, что диапазон исключений, разрешенных для генерирования функции, можно ограничивать только типами, генерируемыми ею в try-блоке, из которого была вызвана. Другими словами, любой try-блок, расположенный в теле самой функции, может генерировать исключения любого типа, если они перехватываются в теле той же функции. Ограничение применяется только для ситуаций, когда " выброс" исключений происходит за пределы функции. Следующее изменение помешает функции Xhandler() генерировать любые изменения. // Эта функция вообще не может генерировать исключения! Void Xhandler(int test) throw() { /* Следующие инструкции больше не работают. Теперь они могут вызвать лишь аварийное завершение программы. */ if(test==0) throw test; if(test==1) throw 'a'; if(test==2) throw 123.23; } На заметку. На момент написания этой книги среда Visual C++ не обеспечивала для функции запрет генерировать исключения, тип которых не задан в throw-выражении. Это говорит о нестандартном поведении данной среды. Тем не менее вы все равно можете задавать " ограничивающее" throw-выражение, но оно в этом случае будет играть лишь уведомительную роль. Повторное генерирование исключения Для того чтобы повторно сгенерировать исключение в его обработчике, воспользуйтесь throw-инструкцией без указания типа исключения. В этом случае текущее исключение будет передано во внешнюю try/catch-последовательность. Чаще всего причиной для такого выполнения инструкции throw служит стремление позволить доступ к одному исключению нескольким обработчикам. Например, первый обработчик исключений будет сообщать об одном аспекте исключения, а второй — о другом. Исключение можно повторно сгенерировать только в catch-блоке (или в любой функции, вызываемой из этого блока). При повторном генерировании исключение не будет перехватываться той же catch-инструкцией. Оно распространится на ближайшую try/catch-последовательность. Повторное генерирование исключения демонстрируется в следующей программе (в данном случае повторно генерируется тип char *). // Пример повторного генерирования исключения. #include < iostream> using namespace std; Void Xhandler() { try { throw " Привет"; // генерирует исключение типа char * } catch(char *) { // перехватывает исключение типа char * cout < < " Перехват исключения в функции Xhandler."; throw; // Повторное генерирование исключения типа char *, которое будет перехвачено вне функции Xhandler. } } Int main() { cout < < " НАЧАЛО"; try { Xhandler(); } catch(char *) { cout < < " Перехват исключения в функции main()."; } cout < < " КОНЕЦ"; return 0; } При выполнении эта программа генерирует такие результаты. НАЧАЛО Перехват исключения в функции Xhandler. Перехват исключения в функции main(). КОНЕЦ Обработка исключений, сгенерированных оператором new В главе 9 вы узнали, что оператор new генерирует исключение, если не удается удовлетворить запрос на выделение памяти. Поскольку тема исключений рассматривается только в этой главе, описание обработки исключений этого типа было отложено " на потом" . Вот теперь настало время об этом поговорить. Для начала необходимо отметить, что в этом разделе описывается поведение оператора new в соответствии со стандартом C++. Как было отмечено в главе 9, действия, выполняемые системой при неуспешном использовании оператора new, с момента изобретения языка C++ изменялись уже несколько раз. Сначала оператор new возвращал при неудаче значение null. Позже такое поведение было заменено генерированием исключения. Кроме того, несколько раз менялось имя этого исключения. Наконец, было решено, что оператор new будет генерировать исключения по умолчанию, но в качестве альтернативного варианта он может возвращать и нулевой указатель. Следовательно, оператор new в разное время был реализован различными способами. И хотя все современные компиляторы реализуют оператор new в соответствии со стандартом C++, компиляторы более " почтенного" возраста могут содержать отклонения от него. Если приведенные здесь примеры программ не работают с вашим компилятором, обратитесь к документации, прилагаемой к компилятору, и поинтересуйтесь, как именно он реализует функционирование оператора new. Согласно стандарту C++ при невозможности удовлетворить запрос на выделение памяти, требуемой оператором new, генерируется исключение типа bad_alloc. Если ваша программа не перехватит его, она будет досрочно завершена. Хотя такое поведение годится для коротких примеров программ, в реальных приложениях необходимо перехватывать это исключение и разумно обрабатывать его. Чтобы получить доступ к исключению типа bad_alloc, нужно включить в программу заголовок < new> . Рассмотрим пример использования оператора new, заключенного в try/catch-блок для отслеживания неудачных результатов запроса на выделение памяти. // Обработка исключений, генерируемых оператором new. #include < iostream> #include < new> using namespace std; Int main() { int *p, i; try { p = new int[32]; // запрос на выделение памяти для 32-элементного int-массива } catch (bad_alloc ха) { cout < < " Память не выделена."; return 1; } for(i=0; i< 32; i++) p[i] = i; for(i=0; i< 32; i++ ) cout < < p[i] < < " "; delete [] p; // освобождение памяти return 0; } При неудачном выполнении оператора new исключение в этой программе будет перехвачено catch-инструкцией. Этот же подход можно использовать для отслеживания любых ошибок, связанных с использованием оператора new: достаточно заключить каждую new-инструкцию в try-блок. Альтернативная форма оператора new — nothrow Стандарт C++ при неудачной попытке выделения памяти вместо генерирования исключения также позволяет оператору new возвращать значение null. Эта форма использования оператора new особенно полезна при компиляции старых программ с применением современного С++-компилятора. Это средство также очень полезно при замене вызовов функции malloc() оператором new. (Это обычная практика при переводе С-кода на язык C++.) Итак, этот формат оператора new выглядит следующим образом. p_var = new(nothrow) тип; Здесь элемент p_var— это указатель на переменную типа тип. Этот nothrow-формат оператора new работает подобно оригинальной версии оператора new, которая использовалась несколько лет назад. Поскольку оператор new (nothrow) возвращает при неудаче значение null, его можно " внедрить" в старый код программы, не прибегая к обработке исключений. Однако в новых программах на C++ все же лучше иметь дело с исключениями. В следующем примере показано, как используется альтернативный вариант new (nothrow). Нетрудно догадаться, что перед вами вариация на тему предыдущей программы. // Использование nothrow-версии оператора new. #include < iostream> #include < new> using namespace std; Int main() { int *p, i; p = new(nothrow) int[32]; // использование nothrow-версии if(! p) { cout < < " Память не выделена."; return 1; } for(i=0; i< 32; i++) p[i] = i; for(i=0; i< 32; i++ ) cout < < p[i] < < " "; delete [] p; // освобождение памяти return 0; } Здесь при использовании nothrow-версии после каждого запроса на выделение памяти необходимо проверять значение указателя, возвращаемого оператором new. Перегрузка операторов new и delete Поскольку new и delete — операторы, их также можно перегружать. Несмотря на то что перегрузку операторов мы рассматривали в главе 13, тема перегрузки операторов new и delete была отложена до знакомства с темой исключений, поскольку правильно перегруженная версия оператора new (та, которая соответствует стандарту C++) должна в случае неудачи генерировать исключение типа bad_alloc. По ряду причин вам имеет смысл создать собственную версию оператора new. Например, создайте процедуры выделения памяти, которые, если область кучи окажется исчерпанной, автоматически начинают использовать дисковый файл в качестве виртуальной памяти. В любом случае реализация перегрузки этих операторов не сложнее перегрузки любых других. Ниже приводится скелет функций, которые перегружают операторы new и delete. // Выделение памяти для объекта. void *operator new(size_t size) { /* В случае невозможности выделить память генерируется исключение типа bad_alloc. Конструктор вызывается автоматически. */ return pointer_to_memory; } // Удаление объекта. void operator delete(void *p) { /* Освобождается память, адресуемая указателем р. Деструктор вызывается автоматически. */ } Тип size_t специально определен, чтобы обеспечить хранение размера максимально возможной области памяти, которая может быть выделена для объекта. (Тип size_t, по сути, —это целочисленный тип без знака.) Параметр size определяет количество байтов памяти, необходимых для хранения объекта, для которого выделяется память. Другими словами, это объем памяти, который должна выделить ваша версия оператора new. Перегруженная функция new должна возвращать указатель на выделяемую ею память или генерировать исключение типа bad_alloc в случае возникновении ошибки. Помимо этих ограничений, перегруженная функция new может выполнять любые нужные действия. При выделении памяти для объекта с помощью оператора new (его исходной версии или вашей собственной) автоматически вызывается конструктор объекта. Функция delete получает указатель на область памяти, которую необходимо освободить. Затем она должна вернуть эту область памяти системе. При удалении объекта автоматически вызывается его деструктор. Чтобы выделить память для массива объектов, а затем освободить ее, необходимо использовать следующие форматы операторов new и delete. // Выделение памяти для массива объектов. void *operator new[](size_t size) { /* В случае невозможности выделить память генерируется исключение типа bad_alloc. Каждый конструктор вызывается автоматически. */ return pointer_to_memory; } // Удаление массива объектов. void operator delete[](void *p) { /* Освобождается память, адресуемая указателем р. При этом автоматически вызывается деструктор для каждого элемента массива. */ } При выделении памяти для массива автоматически вызывается конструктор каждого объекта, а при освобождении массива автоматически вызывается деструктор каждого объекта. Это значит, что для выполнения этих действий не нужно явным образом программировать их. Операторы new и delete, как правило, перегружаются относительно класса. Ради простоты в следующем примере используется не новая схема распределения памяти, а перегруженные функции new и delete, которые просто вызывают С-ориентированные функции выделения памяти malloc() и free(). (В своем собственном приложении вы вольны реализовать любой метод выделения памяти.) Чтобы перегрузить операторы new и delete для конкретного класса, достаточно сделать эти перегруженные операторные функции членами этого класса. В следующем примере программы операторы new и delete перегружаются для класса three_d. Эта перегрузка позволяет выделить память для объектов и массивов объектов, а затем освободить ее. // Демонстрация перегруженных операторов new и delete. #include < iostream> #include < new> #include < cstdlib> using namespace std; class three_d { int x, y, z; // 3-мерные координаты public: three_d() { x = у = z = 0; cout < < " Создание объекта 0, 0, 0"; } three_d(int i, int j, int k) { x = i; у = j; z = k; cout < < " Создание объекта " < < i < < ", "; cout < < j < < ", " < < k; cout < < ''; } ~three_d() { cout < < " Разрушение объекта"; } void *operator new(size_t size); void *operator new[](size_t size); void operator delete(void *p); void operator delete[](void *p); void show(); }; // Перегрузка оператора new для класса three_d. void *three_d:: operator new(size_t size) { void *p; cout < < " Выделение памяти для объекта класса three_d."; р = malloc(size); // Генерирование исключения в случае неудачного выделения памяти. if(! р) { bad_alloc ba; throw ba; } return р; } // Перегрузка оператора new для массива объектов типа three_d. void *three_d:: operator new[](size_t size) { void *p; cout < < " Выделение памяти для массива three_d-oбъeктoв."; cout < < " "; // Генерирование исключения при неудаче. р = malloc(size); if(! р) { bad_alloc ba; throw ba; } return p; } // Перегрузка оператора delete для класса three_d. void three_d:: operator delete(void *p) { cout < < " Удаление объекта класса three_d."; free(p); } // Перегрузка оператора delete для массива объектов типа three_d. void three_d:: operator delete[](void *p) { cout < < " Удаление массива объектов типа three_d."; free(р); } // Отображение координат X, Y, Z. void three_d:: show() { cout < < x < < ", "; cout < < у < < ", "; cout < < z < < " "; } Int main() { three_d *p1, *p2; try { p1 = new three_d[3]; // выделение памяти для массива р2 = new three_d(5, 6, 7); // выделение памяти для объекта } catch (bad_alloc ba) { cout < < " Ошибка при выделении памяти."; return 1; } p1[1].show(); p2-> show(); delete [] p1; // удаление массива delete р2; // удаление объекта return 0; } Популярное:
|
Последнее изменение этой страницы: 2016-03-17; Просмотров: 1520; Нарушение авторского права страницы