Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 93
Текст из файла (страница 93)
Исключения и оператор печу Рассмотрим пример; ио(с(1 Игепай а, Л" ЬиДег) Х' р! = пев Х; Л" р2 = пев Х]10]; О помевпить Л в оуфер — не требуется //освобождения палтти Х' рб = пев )йЬи//ег)!О]) Х, Л" рв = пев )йЬиЦег,'11]) Х]10]; Х' рб = пев ]а) Х, // выделение памяти из «арены» а // (освобождение из а) Л" рб = пев )а) Х]10]; ) Что произойдет, если конструктор Хсгенерирует исключение? Освобождается ли память, выделенная орега(ог пев ))? В обычном случае ответ положителен, поэтому инициализация р1 и р2 не вызывают утечки памяти. Когда используется синтаксис размещения 8 10.4.11), ответ не может быть таким простылс При некотором использовании этого синтаксиса выделенная память затем освобождается, при некотором — нет.
Более того, целью синтаксиса размещения является использование нестандартного выделения памяти, поэтому обычно требуется и нестандартное освобождение. Следовательно, предпринимаемые дспствия завися~ от псполь- 42Э 14.4. Управление ресурсами 14.4.5. Исчерпание ресурсов Периодически возникает вопрос: что делать, когда попытка получения ресурса завершилась неуспешно? Например, мы жизнерадостно открыли файлы 1пспользуя виорел ()) и затребовали память в куче (используя орега1ог пеш), не побеспокоившись о том, что будет, если файла не оказалось на месте или нет свободной памяти, Встретившись с такими проблемами, программисты используют решения двух типов: Возобновление: Попросить вызвавшую функцию устранить проблему н продолжить. Завершение: Прекратить выполнение и возвратиться в вызвавшую программу. В первом случае вызывающая функция должна быть готова оказать помогдь при возникновении (в неизвестном фрагменте кода) проблемы с выделением ресурсов.
Во втором — вызываюгдая функция должна быть готова к провалу попытки выделения ресурса. Второй вариант в болыпннстве случаев намного проще и позволяет обеспечить лучшее разлеленне уровней абстракции. Обратите внимание, что при использовании второго варианта, прекршцается выполнение не всей программы, а только конкретного фрагмента. кЗавершениеь — традиционный термин, означающий стратегию возврата из места, где вычисления закончились неуспешно, в обработчик ошибок, связанньш с вызываюшеи функцией(которая может попытаться заново вызвать неуспешно завершившиеся вычисление), а не попытку устранения проблемы и возобновление выполнения с места, в котором обнаружена проблема. В С++ модель возобновления поддерживается механизмом вызова функций, а модель завершения — механизмом обработки исключений.
Обе модели можно продемонстрировать на простом примере реализации и использования стандартного библиотечного орега1ог лев (): иаи1" арегагпг лет (з1зе 1з1зе) ( Уог (и) ( (1 (пав р = та1!ас (з!ге)) гетагп р; ьУ ( пев Ьаги11ег == О) 1Ь гам Ьаг1 авас (); 11 попытка ипйьпи пилять 11 нет обработчака— 0 прекрапшть УУабратшпься за паяоа1ью пега Ьапг11ег(), В этом примере для поиска свободной памяти я использовал стандартную функцию библиотеки С таПос (); другие реализации орега1ог леш () могут выбрать иной подход, Если память найдена, орега1ог пеш () возвращает указатель на нее, В противном случаеарега1огпеш() вызывает гьеш Ьапг11ег. Если пеш ЬапсПегможетгде то найти память для функции таПас () — все прекрасно. В противном случае обработчик не может возвратиться в орега1огпеш (), не приводя при этом к бесконечному циклу. В результате пеш ЬапсПег () может сгенерировать исключение, предоставив вызывающей программе разбираться с ситуацией: зованного оператора выделения памяти, Если использовался оператор юсорега1ог пеш (), то при выходе применяется Узорега1ог с(е(ебе (); в противном случае не производится попытка освобождения памяти.
Массивы обрабатываются аналогичныьа образом 6 15.6.1). Эта стратегия корректно работает в случае с оператором размещения стандартной библиотеки пеш Я 10.4.11), также как ц в любом другом случае, когда программист реализовал коыплиментарную пару функций выделения/освобождения. Глава 14. Обработка исключений 424 оои1ту пет Ьапс(!ег() ( !п1по оУ Ьу1ее ~олог!=Япг! соте тетогу(); (/ (по о/ Ьу1ее !олпу< тт аПосайоп) тйгого Ьас! ауос (), //сдаюсь Где-то должен быть блок 1гу с соответствующим обработчиком: ггу ( 0- са1сЬ (Ьас! ауос) ( //реакция на псяерпание паяяти В реалпзапии орега1оглеш () используется пеш Ьапг/!ег, которыйявляется указателем на функцию, задаваемую стандартной функцией ве1 пеш Ьалйег(). Если я хочу, чтобы ту пеш Ьапг/!ег() использовался в качестве пеш Ьапйег, я могу напг1сать: ве1 пет Ьапд!ег(ату пет Палс(!ег~; Если, кроме того, я хочу перехватывать Ьас( аПос, я могу наппсатьк оо!ПЯ ( ооЫ (*оЫпЬ) () = ве1 пет Ьапд!ег(йту пего Ьапйег), ггу ( // са1сЬ (Ьад аПос) ( //..
са1сЬ ( )( ее1 пет Ьапйег(оЫпЬ), 1Ьгого, // переустановить обраоот тк // повьпорная генерация // переустановить обре ботяик ее1 пет Палс!!ег(оЫпЬ) А еще лучше вместо са1сЬ (...) применить технику «выделение ресурса есть инициализациягч описанную в ф 14,4, к пеш Ьапйегв (4.12(1)). При использовании лещ Ьалйег не передается никакой дополнительной информации пз места обнаружения ошибки в функциго-помощник. Можно довольно просто передать больше информации. Однако чем больше информации передается между кодом, обнаружившим ошибку времени выполнения, и функцией, помогающей ее устранить, тем больше два фрагмента кода начинают зависеть друг от друга. Из этого следует, что изменения одного фрагмента кода требукьт понимания или модгет быть даже изменения другого.
Для того чтобы отдельные фрагменты кода программы были действительно отдельными, лучше сводить к минимуму подобные зависимостии. Механизм обработки исключений лучше поддерживае~ ~акое разделение, чем вызовы функций-помощников, предоставляемых вызывающей процедурой. Как правило, мудрым решением является разбиение выделения ресурсов на слои !уровни абстракции) и устранение зависимости одного слоя от помощи со стороны 425 14.4. Управление ресурсами слоя, который его вызвал.
Практический опыт создания крупных систем показывает, что успешно работающие системы развиваются в этом направлении. Для генерации исключения требуется объект. К реализации Сев предъявляется требование: объем свободной лама~и всегда должен быть достаточен для генерации бас( айос в случае исчерпания памяти. Однако вполне возможно, что генерация некоторых других исключений приведет к исчерпанию памяти.
14.4.6. Исключения а конструкторах Исключения предоставляют способ решить проблему: как сообщить об ошибке из конструктора. Ввиду того, что конструктор не возвращает отдельного значения, которое вызывающая функция могла бы проверить, традиционнымп (то есть без обработки исключений) альтернативами остаются: [1~ Возвратить объект в «неправильном> состоянии и полагаться на то, что пользователь провери~ его состояние.
(2~ Присвоить значение нелокальной переменной (например, еггпо) для указания на неуспешное создание объекта и полагаться на то, что пользователь его проверит. Щ Не осуществлять никакой инициализации в конструкторе и полагаться на то, что пользователь вызовет функцию инициализации до первого использования. '(4] Пометить объек~ как неинициализированный и при первом вызове функции-члена лля этого объекта осуществить инициализацию (такая функция — не конструктор — может вернуть сообщение об ошибке в случае неуспешной инициализации). Обработка исключений позволяет передать информацию о неуспешной инициализации из конструктора. Например, простой класс Ъес(ос мог бы защититься от запроса слишком большого количества памяти следующим образом: с(авв (гес(ог ( риЬИс с! аз в 5 сев ( ), ел и т ( тих = 32000 ); ЪесГог (сас ее( ( ((' (ее < 0 (~ шах < ве( Г() гош Яссе ((, 0'. 0-.
Код, создающий вектора, теперь может перехватывать ошибки )геолог.:Иге, и мы можем попытаться сделать с ними что-нибудь осмысленное: (гесГог")'((п(й ( ггу ( Ъгессо г' р = пеш Ъ1есГог (й; 0- гесигп р; саГсп ((гесГог,5Гее(( О оорапотка овшбки размера вектора ) Глава 14. Обработка исключений 14.4.6.1. Исключения и инициализация членов Что происходит, когда код, инициализирующий член (непосредственно илн косвенно), генерирует исключение? По умолчанию исключение передается туда, где вызван конструктор для класса этого члена.